codebridge-mcp 1.0.0__py3-none-any.whl

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.
codebridge/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
codebridge/__main__.py ADDED
@@ -0,0 +1,29 @@
1
+ from .server import start
2
+ import sys, os
3
+
4
+ def main():
5
+ """Entry point for CodeBridge CLI."""
6
+ args = sys.argv[1:]
7
+
8
+ if not args or args[0] != "start":
9
+ print("\n⚡ CodeBridge from Bunchhh")
10
+ print("Usage:")
11
+ print(" codebridge start ← uses current folder")
12
+ print(" codebridge start /path/to/project\n")
13
+ return
14
+
15
+ # Get project path — default to current directory
16
+ if len(args) > 1:
17
+ path = args[1]
18
+ if not os.path.isdir(path):
19
+ print(f"Error: Folder not found — {path}")
20
+ return
21
+ else:
22
+ path = os.getcwd() # ← FIX: use current folder by default
23
+
24
+ import codebridge.server as s
25
+ s.PROJECT = os.path.abspath(path)
26
+ start()
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -0,0 +1,48 @@
1
+ """
2
+ QR Code generator for CodeBridge terminal.
3
+ Adds QR code to terminal output so user can scan from phone.
4
+ """
5
+
6
+ def generate_qr_terminal(url):
7
+ """Generate ASCII QR code for terminal display."""
8
+ try:
9
+ import qrcode
10
+ qr = qrcode.QRCode(
11
+ version=1,
12
+ error_correction=qrcode.constants.ERROR_CORRECT_L,
13
+ box_size=1,
14
+ border=1,
15
+ )
16
+ qr.add_data(url)
17
+ qr.make(fit=True)
18
+
19
+ # Print as ASCII blocks
20
+ matrix = qr.get_matrix()
21
+ print("\n Scan to open on mobile:\n")
22
+ for row in matrix:
23
+ line = " "
24
+ for cell in row:
25
+ line += "██" if cell else " "
26
+ print(line)
27
+ print()
28
+ except ImportError:
29
+ # qrcode not installed — skip silently
30
+ pass
31
+
32
+
33
+ def generate_qr_svg(url, output_path="qr.svg"):
34
+ """Generate SVG QR code for dashboard."""
35
+ try:
36
+ import qrcode
37
+ import qrcode.image.svg
38
+ factory = qrcode.image.svg.SvgImage
39
+ img = qrcode.make(url, image_factory=factory)
40
+ img.save(output_path)
41
+ return output_path
42
+ except ImportError:
43
+ return None
44
+
45
+
46
+ # ── Standalone test ─────────────────────────────────────────────
47
+ if __name__ == "__main__":
48
+ generate_qr_terminal("http://192.168.1.5:7777")
codebridge/server.py ADDED
@@ -0,0 +1,483 @@
1
+ """
2
+ CodeBridge — Connect your project to any AI.
3
+ One command. One code. Any model.
4
+ """
5
+
6
+ import os, json, subprocess, socket, threading, uuid
7
+ import webbrowser, random, string
8
+ from http.server import HTTPServer, BaseHTTPRequestHandler
9
+ from urllib.parse import urlparse
10
+
11
+ # ── State ──────────────────────────────────────────────────
12
+ PROJECT = os.getcwd()
13
+ CODE = "CB-" + ''.join(random.choices("ABCDEFGHJKLMNPQRSTUVWXYZ23456789", k=4)) + \
14
+ "-" + ''.join(random.choices("ABCDEFGHJKLMNPQRSTUVWXYZ23456789", k=4))
15
+ WORKSPACE_ID = str(uuid.uuid4())[:8].upper()
16
+ PORT = 7777
17
+ LOG = [] # change history
18
+
19
+ IGNORE = {"__pycache__", "node_modules", ".git", "venv",
20
+ ".next", "dist", "build", ".env"}
21
+ EXTS = {".py",".js",".ts",".jsx",".tsx",".json",".yaml",
22
+ ".yml",".md",".txt",".html",".css",".sh",".toml"}
23
+
24
+ # ── File tools ─────────────────────────────────────────────
25
+ def get_files():
26
+ out = []
27
+ for root, dirs, files in os.walk(PROJECT):
28
+ dirs[:] = [d for d in dirs if d not in IGNORE]
29
+ for f in sorted(files):
30
+ if os.path.splitext(f)[1].lower() in EXTS:
31
+ rel = os.path.relpath(os.path.join(root, f), PROJECT)
32
+ out.append(rel.replace("\\", "/"))
33
+ return out[:80]
34
+
35
+ def read_file(path):
36
+ full = os.path.join(PROJECT, path.replace("/", os.sep))
37
+ if not os.path.exists(full):
38
+ return f"ERROR: File not found — {path}"
39
+ with open(full, "r", encoding="utf-8", errors="replace") as f:
40
+ return f.read()
41
+
42
+ def write_file(path, content):
43
+ full = os.path.join(PROJECT, path.replace("/", os.sep))
44
+ if not os.path.abspath(full).startswith(os.path.abspath(PROJECT)):
45
+ return "ERROR: Cannot write outside project."
46
+ # Backup
47
+ if os.path.exists(full):
48
+ open(full + ".bak", "w").write(open(full).read())
49
+ os.makedirs(os.path.dirname(full), exist_ok=True)
50
+ open(full, "w", encoding="utf-8").write(content)
51
+ LOG.append({"action": "fixed", "file": path})
52
+ return f"OK: {path} saved."
53
+
54
+ def run_cmd(cmd):
55
+ SAFE = ["python","pytest","npm","node","pip","git","ls","cat","echo","uvicorn","celery"]
56
+ if not any(cmd.strip().startswith(s) for s in SAFE):
57
+ return f"ERROR: Command not allowed — {cmd}"
58
+ try:
59
+ r = subprocess.run(cmd, shell=True, cwd=PROJECT,
60
+ capture_output=True, text=True, timeout=30)
61
+ LOG.append({"action": "ran", "cmd": cmd})
62
+ return (r.stdout + r.stderr)[-2000:]
63
+ except Exception as e:
64
+ return f"ERROR: {e}"
65
+
66
+ def undo(path):
67
+ full = os.path.join(PROJECT, path.replace("/", os.sep))
68
+ bak = full + ".bak"
69
+ if not os.path.exists(bak):
70
+ return f"ERROR: No backup for {path}"
71
+ open(full, "w").write(open(bak).read())
72
+ os.remove(bak)
73
+ return f"OK: {path} restored."
74
+
75
+ # ── MCP tool definitions ────────────────────────────────────
76
+ TOOLS = [
77
+ {
78
+ "name": "list_files",
79
+ "description": "List all code files in the connected project.",
80
+ "inputSchema": {"type": "object", "properties": {}}
81
+ },
82
+ {
83
+ "name": "read_file",
84
+ "description": "Read a file from the project. Always read before fixing.",
85
+ "inputSchema": {
86
+ "type": "object",
87
+ "properties": {
88
+ "path": {"type": "string", "description": "e.g. src/main.py"}
89
+ },
90
+ "required": ["path"]
91
+ }
92
+ },
93
+ {
94
+ "name": "write_file",
95
+ "description": "Write a fix to a file. Always provide the COMPLETE file content.",
96
+ "inputSchema": {
97
+ "type": "object",
98
+ "properties": {
99
+ "path": {"type": "string"},
100
+ "content": {"type": "string"}
101
+ },
102
+ "required": ["path", "content"]
103
+ }
104
+ },
105
+ {
106
+ "name": "run_command",
107
+ "description": "Run a terminal command (python, pytest, npm, git...).",
108
+ "inputSchema": {
109
+ "type": "object",
110
+ "properties": {
111
+ "command": {"type": "string"}
112
+ },
113
+ "required": ["command"]
114
+ }
115
+ },
116
+ {
117
+ "name": "undo",
118
+ "description": "Undo the last AI fix to a file.",
119
+ "inputSchema": {
120
+ "type": "object",
121
+ "properties": {
122
+ "path": {"type": "string"}
123
+ },
124
+ "required": ["path"]
125
+ }
126
+ }
127
+ ]
128
+
129
+ def run_tool(name, args):
130
+ if name == "list_files": return {"files": get_files()}
131
+ if name == "read_file": return {"content": read_file(args.get("path",""))}
132
+ if name == "write_file": return {"result": write_file(args.get("path",""), args.get("content",""))}
133
+ if name == "run_command": return {"output": run_cmd(args.get("command",""))}
134
+ if name == "undo": return {"result": undo(args.get("path",""))}
135
+ return {"error": f"Unknown tool: {name}"}
136
+
137
+ # ── HTTP server ─────────────────────────────────────────────
138
+ class H(BaseHTTPRequestHandler):
139
+ def log_message(self, *a): pass
140
+
141
+ def cors(self):
142
+ self.send_header("Access-Control-Allow-Origin", "*")
143
+ self.send_header("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
144
+ self.send_header("Access-Control-Allow-Headers", "Content-Type,Authorization")
145
+
146
+ def do_OPTIONS(self):
147
+ self.send_response(204); self.cors(); self.end_headers()
148
+
149
+ def json(self, data, code=200):
150
+ b = json.dumps(data, indent=2).encode()
151
+ self.send_response(code)
152
+ self.send_header("Content-Type", "application/json")
153
+ self.send_header("Content-Length", str(len(b)))
154
+ self.cors(); self.end_headers(); self.wfile.write(b)
155
+
156
+ def body(self):
157
+ n = int(self.headers.get("Content-Length", 0))
158
+ return json.loads(self.rfile.read(n)) if n else {}
159
+
160
+ def do_GET(self):
161
+ p = urlparse(self.path).path
162
+
163
+ if p in ("/", "/index.html"):
164
+ h = dashboard()
165
+ self.send_response(200)
166
+ self.send_header("Content-Type", "text/html")
167
+ self.end_headers()
168
+ self.wfile.write(h.encode())
169
+
170
+ elif p == "/mcp":
171
+ # MCP discovery — what AI platforms call first
172
+ self.json({
173
+ "name": "CodeBridge",
174
+ "version": "1.0.0",
175
+ "connection_code": CODE,
176
+ "workspace_id": WORKSPACE_ID,
177
+ "description": "Your project is connected. I can read files, write fixes, run commands.",
178
+ "tools": TOOLS
179
+ })
180
+
181
+ elif p == "/workspace":
182
+ self.json({
183
+ "workspace_id": WORKSPACE_ID,
184
+ "project": os.path.basename(PROJECT),
185
+ "path": PROJECT,
186
+ "files": len(get_files())
187
+ })
188
+
189
+ elif p == "/status":
190
+ self.json({
191
+ "connected": True,
192
+ "code": CODE,
193
+ "workspace_id": WORKSPACE_ID,
194
+ "project": os.path.basename(PROJECT),
195
+ "path": PROJECT,
196
+ "files": len(get_files()),
197
+ "log": LOG[-10:]
198
+ })
199
+
200
+ elif p == "/mobile":
201
+ self.send_response(200)
202
+ self.send_header("Content-Type", "text/html")
203
+ self.end_headers()
204
+ self.wfile.write(mobile_scanner().encode())
205
+
206
+ else:
207
+ self.json({"error": "not found"}, 404)
208
+
209
+ def do_POST(self):
210
+ p = urlparse(self.path).path
211
+ b = self.body()
212
+
213
+ if p == "/mcp":
214
+ # AI platform calls a tool
215
+ name = b.get("name") or b.get("tool", "")
216
+ args = b.get("arguments") or b.get("input") or {}
217
+ result = run_tool(name, args)
218
+ self.json({"content": [{"type": "text", "text": json.dumps(result)}]})
219
+
220
+ else:
221
+ self.json({"error": "not found"}, 404)
222
+
223
+
224
+ # ── Dashboard HTML ──────────────────────────────────────────
225
+ def dashboard():
226
+ ip = socket.gethostbyname(socket.gethostname())
227
+ files = get_files()
228
+ file_tags = "".join(
229
+ f'<span style="padding:3px 10px;background:rgba(255,255,255,0.04);'
230
+ f'border:1px solid rgba(255,255,255,0.08);border-radius:20px;'
231
+ f'font-size:11px;font-family:monospace;color:#8B9E7A">{f}</span>'
232
+ for f in files[:20]
233
+ )
234
+
235
+ return f"""<!DOCTYPE html>
236
+ <html lang="en"><head>
237
+ <meta charset="UTF-8">
238
+ <meta name="viewport" content="width=device-width,initial-scale=1">
239
+ <title>CodeBridge ⚡</title>
240
+ <style>
241
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&family=DM+Mono:wght@400;500&display=swap');
242
+ *{{box-sizing:border-box;margin:0;padding:0}}
243
+ body{{background:#0D0F0B;color:#C4CDB8;font-family:'DM Sans',sans-serif;
244
+ min-height:100vh;padding:24px 16px;max-width:560px;margin:0 auto}}
245
+ h1{{font-size:22px;font-weight:600;color:#E4EDD8;margin-bottom:4px}}
246
+ .mono{{font-family:'DM Mono',monospace}}
247
+ .card{{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);
248
+ border-radius:12px;padding:18px;margin-bottom:14px}}
249
+ .tag-green{{display:inline-block;padding:3px 10px;border-radius:20px;font-size:11px;
250
+ background:rgba(116,180,100,0.1);border:1px solid rgba(116,180,100,0.3);color:#74B464}}
251
+ .code-box{{background:rgba(0,0,0,0.4);border:1px solid rgba(255,255,255,0.1);
252
+ border-radius:10px;padding:16px;text-align:center}}
253
+ .big-code{{font-size:32px;font-weight:600;letter-spacing:5px;color:#A8D490;
254
+ font-family:'DM Mono',monospace}}
255
+ .step{{display:flex;gap:12px;align-items:flex-start;padding:10px 0;
256
+ border-bottom:1px solid rgba(255,255,255,0.04)}}
257
+ .step:last-child{{border-bottom:none}}
258
+ .num{{width:24px;height:24px;border-radius:50%;background:rgba(168,212,144,0.1);
259
+ border:1px solid rgba(168,212,144,0.3);display:flex;align-items:center;
260
+ justify-content:center;font-size:11px;color:#A8D490;flex-shrink:0;font-weight:600}}
261
+ .label{{font-size:13px;color:#E4EDD8;font-weight:500;margin-bottom:2px}}
262
+ .sub{{font-size:12px;color:#6B7A60}}
263
+ .files{{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}}
264
+ .log-item{{font-size:12px;padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.04);
265
+ font-family:'DM Mono',monospace;color:#8B9E7A}}
266
+ @keyframes pulse{{0%,100%{{opacity:.5}}50%{{opacity:1}}}}
267
+ .live-dot{{width:7px;height:7px;border-radius:50%;background:#74B464;
268
+ animation:pulse 2s ease infinite;display:inline-block;margin-right:6px}}
269
+ </style>
270
+ </head><body>
271
+
272
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:24px">
273
+ <div style="display:flex;align-items:center;gap:10px">
274
+ <div style="width:34px;height:34px;background:linear-gradient(135deg,#A8D490,#4A8A30);
275
+ border-radius:9px;display:flex;align-items:center;justify-content:center;
276
+ font-size:17px">⚡</div>
277
+ <div>
278
+ <h1>CodeBridge</h1>
279
+ <div style="font-size:10px;color:#3D4A30;letter-spacing:.1em">by <a href='https://bunchhh.com' style='color:#A8D490;text-decoration:none;font-weight:600'>Bunchhh</a> • AI CONNECTOR</div>
280
+ </div>
281
+ </div>
282
+ <span class="tag-green"><span class="live-dot"></span>LIVE</span>
283
+ </div>
284
+
285
+ <!-- Project -->
286
+ <div class="card">
287
+ <div style="font-size:10px;color:#3D4A30;letter-spacing:.1em;margin-bottom:8px">CONNECTED PROJECT</div>
288
+ <div style="font-size:16px;font-weight:600;color:#E4EDD8">{os.path.basename(PROJECT)}</div>
289
+ <div class="mono" style="font-size:11px;color:#6B7A60;margin-top:2px">{PROJECT}</div>
290
+ <div style="font-size:12px;color:#74B464;margin-top:6px">{len(files)} files indexed</div>
291
+ </div>
292
+
293
+ <!-- Connection Code -->
294
+ <div class="card" style="border-color:rgba(168,212,144,0.2)">
295
+ <div style="font-size:10px;color:#3D4A30;letter-spacing:.1em;margin-bottom:10px">YOUR CONNECTION CODE</div>
296
+ <div class="code-box">
297
+ <div class="big-code">{CODE}</div>
298
+ <div style="font-size:11px;color:#6B7A60;margin-top:8px">Paste this into Claude / ChatGPT / Gemini</div>
299
+ </div>
300
+ <div style="margin-top:12px;padding:12px;background:rgba(0,0,0,0.2);border-radius:8px">
301
+ <div style="font-size:10px;color:#3D4A30;margin-bottom:4px">WORKSPACE ID (for Extension)</div>
302
+ <div style="font-size:18px;font-weight:600;color:#A8D490;font-family:'DM Mono',monospace">{WORKSPACE_ID}</div>
303
+ <div style="font-size:11px;color:#6B7A60;margin-top:4px">Use this in VS Code extension to connect</div>
304
+ </div>
305
+ </div>
306
+
307
+ <!-- How to connect -->
308
+ <div class="card">
309
+ <div style="font-size:10px;color:#3D4A30;letter-spacing:.1em;margin-bottom:12px">HOW TO CONNECT YOUR AI</div>
310
+
311
+ <div class="step">
312
+ <div class="num">1</div>
313
+ <div>
314
+ <div class="label">Open Claude.ai / ChatGPT / Gemini</div>
315
+ <div class="sub">Any free account works</div>
316
+ </div>
317
+ </div>
318
+
319
+ <div class="step">
320
+ <div class="num">2</div>
321
+ <div>
322
+ <div class="label">Add MCP integration</div>
323
+ <div class="sub" style="margin-bottom:6px">Settings → Integrations → Add MCP</div>
324
+ <div style="display:flex;align-items:center;gap:8px">
325
+ <code style="font-size:11px;background:rgba(0,0,0,0.4);padding:4px 10px;
326
+ border-radius:6px;color:#A8D490;font-family:'DM Mono',monospace">
327
+ http://{ip}:{PORT}/mcp
328
+ </code>
329
+ <button onclick="navigator.clipboard.writeText('http://{ip}:{PORT}/mcp');this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',2000)"
330
+ style="padding:4px 12px;border-radius:6px;background:rgba(168,212,144,0.1);
331
+ border:1px solid rgba(168,212,144,0.3);color:#A8D490;font-size:11px;cursor:pointer">
332
+ Copy
333
+ </button>
334
+ </div>
335
+ </div>
336
+ </div>
337
+
338
+ <div class="step">
339
+ <div class="num">3</div>
340
+ <div>
341
+ <div class="label">See the ⚡ logo appear</div>
342
+ <div class="sub">That means AI is connected to your project</div>
343
+ </div>
344
+ </div>
345
+
346
+ <div class="step">
347
+ <div class="num" style="background:rgba(116,180,100,0.15);border-color:rgba(116,180,100,0.4);color:#74B464">✓</div>
348
+ <div>
349
+ <div class="label" style="color:#74B464">Talk to AI — it fixes your code directly</div>
350
+ <div class="sub">No copy-paste. Changes save automatically.</div>
351
+ </div>
352
+ </div>
353
+ </div>
354
+
355
+ <!-- Files -->
356
+ <div class="card">
357
+ <div style="font-size:10px;color:#3D4A30;letter-spacing:.1em;margin-bottom:10px">PROJECT FILES AI CAN SEE</div>
358
+ <div class="files">{file_tags if file_tags else '<span style="color:#3D4A30;font-size:12px">No code files found</span>'}</div>
359
+ </div>
360
+
361
+ <!-- Change log -->
362
+ <div class="card" id="log-card">
363
+ <div style="font-size:10px;color:#3D4A30;letter-spacing:.1em;margin-bottom:10px">AI CHANGE LOG</div>
364
+ <div id="log-body" style="font-size:12px;color:#3D4A30">No changes yet.</div>
365
+ </div>
366
+
367
+ <!-- What AI can do -->
368
+ <div class="card">
369
+ <div style="font-size:10px;color:#3D4A30;letter-spacing:.1em;margin-bottom:10px">WHAT AI CAN DO NOW</div>
370
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
371
+ {"".join(f'<div style="padding:8px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.05);border-radius:8px;font-size:12px"><span style="color:#A8D490">{icon}</span> <span style="color:#C4CDB8">{label}</span></div>' for icon, label in [("📄","Read any file"),("✏️","Write fixes"),("🔍","Search code"),("▶️","Run commands"),("↩️","Undo changes"),("📱","Mobile control")])}
372
+ </div>
373
+ </div>
374
+
375
+ <div style="text-align:center;font-size:11px;color:#2D3A20;padding:20px 0">
376
+ CodeBridge by <a href='https://bunchhh.com' style='color:#A8D490;text-decoration:none'>Bunchhh</a> · <a href='https://bunchhh.com' style='color:#6B7A60;text-decoration:none'>Visit Bunchhh</a> · Press Ctrl+C to stop
377
+ </div>
378
+
379
+ <script>
380
+ async function refreshLog() {{
381
+ const r = await fetch('/status');
382
+ const d = await r.json();
383
+ const el = document.getElementById('log-body');
384
+ if (!d.log || d.log.length === 0) {{
385
+ el.innerHTML = '<span style="color:#3D4A30">No changes yet. Start talking to your AI.</span>';
386
+ return;
387
+ }}
388
+ el.innerHTML = d.log.reverse().map(l =>
389
+ l.action === 'fixed'
390
+ ? `<div class="log-item" style="color:#74B464">✅ Fixed → ${{l.file}}</div>`
391
+ : `<div class="log-item">▶ Ran → ${{l.cmd}}</div>`
392
+ ).join('');
393
+ }}
394
+ refreshLog();
395
+ setInterval(refreshLog, 4000);
396
+ </script>
397
+ </body></html>"""
398
+
399
+ # ── Mobile Scanner HTML ─────────────────────────────────────
400
+ def mobile_scanner():
401
+ ip = socket.gethostbyname(socket.gethostname())
402
+ return f"""<!DOCTYPE html>
403
+ <html lang="en"><head>
404
+ <meta charset="UTF-8">
405
+ <meta name="viewport" content="width=device-width,initial-scale=1">
406
+ <title>Mobile Scanner - CodeBridge</title>
407
+ <style>
408
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&family=DM+Mono:wght@400;500&display=swap');
409
+ *{{box-sizing:border-box;margin:0;padding:0}}
410
+ body{{background:#0D0F0B;color:#C4CDB8;font-family:'DM Sans',sans-serif;
411
+ min-height:100vh;padding:20px;max-width:500px;margin:0 auto}}
412
+ h1{{font-size:20px;font-weight:600;color:#E4EDD8;margin-bottom:16px}}
413
+ .scan-area{{background:rgba(255,255,255,0.03);border:2px dashed rgba(168,212,144,0.3);
414
+ border-radius:12px;padding:30px;text-align:center;margin-bottom:20px}}
415
+ .workspace-id{{font-size:32px;font-weight:600;letter-spacing:8px;color:#A8D490;
416
+ font-family:'DM Mono',monospace;margin:20px 0}}
417
+ .btn{{display:block;width:100%;padding:14px;background:rgba(168,212,144,0.15);
418
+ border:1px solid rgba(168,212,144,0.4);border-radius:8px;color:#A8D490;
419
+ font-size:14px;font-weight:500;margin:10px 0;text-decoration:none}}
420
+ .qr-code{{width:180px;height:180px;background:rgba(255,255,255,0.05);
421
+ margin:20px auto;border-radius:12px;display:flex;align-items:center;
422
+ justify-content:center;font-size:40px}}
423
+ </style>
424
+ </head><body>
425
+
426
+ <h1>📱 Mobile Scanner</h1>
427
+
428
+ <div class="qr-code">🔲</div>
429
+
430
+ <div class="scan-area">
431
+ <div style="font-size:12px;color:#3D4A30;margin-bottom:8px">WORKSPACE ID</div>
432
+ <div class="workspace-id">{WORKSPACE_ID}</div>
433
+ <div style="font-size:11px;color:#6B7A60;margin-top:8px">Connect from your phone</div>
434
+ </div>
435
+
436
+ <a href="http://{ip}:{PORT}" class="btn">Open Dashboard</a>
437
+ <a href="http://{ip}:{PORT}/workspace" class="btn">Connect with AI</a>
438
+
439
+ <div style="margin-top:20px;font-size:11px;color:#6B7A60;text-align:center">
440
+ Visit http://{ip}:{PORT}/mobile on your phone<br>or scan QR code when available
441
+ </div>
442
+
443
+ </body></html>"""
444
+
445
+
446
+ # ── Start ───────────────────────────────────────────────────
447
+ def start():
448
+ ip = socket.gethostbyname(socket.gethostname())
449
+ srv = HTTPServer(("0.0.0.0", PORT), H)
450
+
451
+ print(f"""
452
+ ╔══════════════════════════════════════════════╗
453
+ ║ ⚡ CodeBridge by Bunchhh ⚡ ║
454
+ ║ democratizing-ai-powered-development ║
455
+ ╠══════════════════════════════════════════════╣
456
+ ║ Project : {os.path.basename(PROJECT):<28} ║
457
+ ║ Workspace : {WORKSPACE_ID:<28} ║
458
+ ║ Code : {CODE:<30} ║
459
+ ╠══════════════════════════════════════════════╣
460
+ ║ Dashboard : http://localhost:{PORT} ║
461
+ ║ Mobile : http://{ip}:{PORT} ║
462
+ ║ MCP URL : http://{ip}:{PORT}/mcp ║
463
+ ║ Workspace : http://{ip}:{PORT}/workspace ║
464
+ ╠══════════════════════════════════════════════╣
465
+ ║ 1. Copy the MCP URL above ║
466
+ ║ 2. Paste into Claude / ChatGPT / Gemini ║
467
+ ║ 3. See ⚡ logo → start talking ║
468
+ ╠══════════════════════════════════════════════╣
469
+ ║ For Extension: use workspace ID {WORKSPACE_ID} ║
470
+ ║ For Mobile: visit http://{ip}:{PORT} on phone ║
471
+ ║ ║
472
+ ║ Learn more: https://bunchhh.com ║
473
+ ╚══════════════════════════════════════════════╝
474
+ Press Ctrl+C to stop.
475
+ """)
476
+ threading.Timer(1.5, lambda: webbrowser.open(f"http://localhost:{PORT}")).start()
477
+ try:
478
+ srv.serve_forever()
479
+ except KeyboardInterrupt:
480
+ print("\n⚡ CodeBridge stopped.")
481
+
482
+ if __name__ == "__main__":
483
+ start()
codebridge/tools.py ADDED
@@ -0,0 +1,143 @@
1
+ """
2
+ All the things AI can do with the connected project.
3
+ Read, write, search, run, understand.
4
+ """
5
+
6
+ import os, subprocess, json, fnmatch
7
+
8
+ IGNORE = {"__pycache__", "node_modules", ".git", "venv", ".env",
9
+ "dist", "build", ".next", ".nuxt", "coverage"}
10
+ CODE_EXTS = {".py", ".js", ".ts", ".jsx", ".tsx", ".json", ".yaml",
11
+ ".yml", ".env", ".md", ".txt", ".html", ".css", ".go",
12
+ ".rs", ".java", ".cpp", ".c", ".sh", ".toml", ".ini", ".cfg"}
13
+
14
+ def list_files(project_path: str) -> list:
15
+ """Return full file tree of the project."""
16
+ result = []
17
+ for root, dirs, files in os.walk(project_path):
18
+ dirs[:] = sorted([d for d in dirs if d not in IGNORE])
19
+ rel_root = os.path.relpath(root, project_path)
20
+ for f in sorted(files):
21
+ ext = os.path.splitext(f)[1].lower()
22
+ if ext in CODE_EXTS:
23
+ rel = os.path.join(rel_root, f) if rel_root != "." else f
24
+ full = os.path.join(root, f)
25
+ size = os.path.getsize(full)
26
+ result.append({
27
+ "name": f,
28
+ "path": rel,
29
+ "full_path": full,
30
+ "ext": ext,
31
+ "size_kb": round(size / 1024, 1),
32
+ })
33
+ return result
34
+
35
+ def read_file(project_path: str, rel_path: str) -> dict:
36
+ """Read a file from the project."""
37
+ full = os.path.join(project_path, rel_path)
38
+ if not os.path.exists(full):
39
+ return {"error": f"File not found: {rel_path}"}
40
+ if os.path.getsize(full) > 500_000: # 500KB limit
41
+ return {"error": "File too large (>500KB). Try a specific section."}
42
+ with open(full, "r", encoding="utf-8", errors="replace") as f:
43
+ content = f.read()
44
+ lines = content.splitlines()
45
+ return {
46
+ "path": rel_path,
47
+ "content": content,
48
+ "lines": len(lines),
49
+ "size_kb": round(os.path.getsize(full) / 1024, 1),
50
+ }
51
+
52
+ def write_file(project_path: str, rel_path: str, content: str) -> dict:
53
+ """Write a fix directly to a file in the project."""
54
+ full = os.path.join(project_path, rel_path)
55
+ # Safety: must be inside project folder
56
+ if not os.path.abspath(full).startswith(os.path.abspath(project_path)):
57
+ return {"error": "Cannot write outside project folder."}
58
+ os.makedirs(os.path.dirname(full), exist_ok=True)
59
+ # Backup original
60
+ backup = full + ".cb_backup"
61
+ if os.path.exists(full):
62
+ with open(full, "r", encoding="utf-8", errors="replace") as f:
63
+ original = f.read()
64
+ with open(backup, "w", encoding="utf-8") as f:
65
+ f.write(original)
66
+ with open(full, "w", encoding="utf-8") as f:
67
+ f.write(content)
68
+ return {"success": True, "path": rel_path, "backup": backup}
69
+
70
+ def search_code(project_path: str, query: str) -> dict:
71
+ """Search for text across all project files."""
72
+ results = []
73
+ files = list_files(project_path)
74
+ for file_info in files:
75
+ try:
76
+ with open(file_info["full_path"], "r", encoding="utf-8", errors="replace") as f:
77
+ lines = f.readlines()
78
+ for i, line in enumerate(lines, 1):
79
+ if query.lower() in line.lower():
80
+ results.append({
81
+ "file": file_info["path"],
82
+ "line": i,
83
+ "content": line.strip(),
84
+ })
85
+ if len(results) >= 50:
86
+ break
87
+ except:
88
+ continue
89
+ if len(results) >= 50:
90
+ break
91
+ return {"query": query, "results": results, "count": len(results)}
92
+
93
+ def run_command(project_path: str, command: str) -> dict:
94
+ """Run a terminal command inside the project folder."""
95
+ ALLOWED = ["python", "pytest", "npm", "node", "pip", "ls", "cat",
96
+ "echo", "git status", "git log", "git diff", "uvicorn",
97
+ "celery", "docker", "curl"]
98
+ safe = any(command.strip().startswith(a) for a in ALLOWED)
99
+ if not safe:
100
+ return {"error": f"Command not allowed for safety: {command}"}
101
+ try:
102
+ result = subprocess.run(
103
+ command, shell=True, cwd=project_path,
104
+ capture_output=True, text=True, timeout=30
105
+ )
106
+ return {
107
+ "command": command,
108
+ "stdout": result.stdout[-3000:], # last 3000 chars
109
+ "stderr": result.stderr[-1000:],
110
+ "exit_code": result.returncode,
111
+ }
112
+ except subprocess.TimeoutExpired:
113
+ return {"error": "Command timed out (30s)"}
114
+ except Exception as e:
115
+ return {"error": str(e)}
116
+
117
+ def undo_last_fix(project_path: str, rel_path: str) -> dict:
118
+ """Restore a file to before the last AI fix."""
119
+ full = os.path.join(project_path, rel_path)
120
+ backup = full + ".cb_backup"
121
+ if not os.path.exists(backup):
122
+ return {"error": "No backup found for this file."}
123
+ with open(backup, "r") as f:
124
+ original = f.read()
125
+ with open(full, "w") as f:
126
+ f.write(original)
127
+ os.remove(backup)
128
+ return {"success": True, "restored": rel_path}
129
+
130
+ def get_project_summary(project_path: str) -> dict:
131
+ """Quick summary of the project structure."""
132
+ files = list_files(project_path)
133
+ by_ext = {}
134
+ for f in files:
135
+ ext = f["ext"]
136
+ by_ext[ext] = by_ext.get(ext, 0) + 1
137
+ total_size = sum(f["size_kb"] for f in files)
138
+ return {
139
+ "total_files": len(files),
140
+ "total_size_kb": round(total_size, 1),
141
+ "by_type": by_ext,
142
+ "files": [f["path"] for f in files[:100]],
143
+ }
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: codebridge-mcp
3
+ Version: 1.0.0
4
+ Summary: CodeBridge by Bunchhh - Connect your project to any AI. Read, fix, build — no copy-paste. Free AI-powered development.
5
+ License: MIT
6
+ Project-URL: Homepage, https://bunchhh.com
7
+ Project-URL: GitHub, https://github.com/bunchhh/codebridge
8
+ Project-URL: Issues, https://github.com/bunchhh/codebridge/issues
9
+ Project-URL: Website, https://bunchhh.com
10
+ Keywords: ai,mcp,claude,chatgpt,gemini,developer-tools,code-assistant,bunchhh
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Programming Language :: Python :: 3
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
@@ -0,0 +1,10 @@
1
+ codebridge/__init__.py,sha256=RvTiA7qML-ZD1Be0T-knTuVQjQIOQj_gte_oBtHDrIg,24
2
+ codebridge/__main__.py,sha256=lNLInS06zEsv8_-G-_WdhmHQkROF2Uz_rsc6uYquhTU,818
3
+ codebridge/qr_generator.py,sha256=FRB05TU6-s4atRb9jVZlEnrEibJhLNJxHQK4-Sfy5Ss,1467
4
+ codebridge/server.py,sha256=reejOeqyyezWWkbHEXMRY1_HCqeR62PNUgpHMKxIYEY,21618
5
+ codebridge/tools.py,sha256=dg1WKfVBa17C8GAmLIU11SR-my3-h3zxKj7OXu7j6kY,5694
6
+ codebridge_mcp-1.0.0.dist-info/METADATA,sha256=CXfBrOYUwIXsf-WdiulT-ll1xz7IXXdPlHlyfHhTvq8,776
7
+ codebridge_mcp-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
+ codebridge_mcp-1.0.0.dist-info/entry_points.txt,sha256=A0bTXF6K2qrqy0u3UQ-fy0_mEehpShRV7jNe6qAMH3g,56
9
+ codebridge_mcp-1.0.0.dist-info/top_level.txt,sha256=WowoBNWSFjcWmYgGIIIHZHZ_cz6NiFlhO_xYv2mrJYY,11
10
+ codebridge_mcp-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codebridge = codebridge.__main__:main
@@ -0,0 +1 @@
1
+ codebridge