codebridge-mcp 1.0.0__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.
- codebridge_mcp-1.0.0/PKG-INFO +16 -0
- codebridge_mcp-1.0.0/codebridge/__init__.py +1 -0
- codebridge_mcp-1.0.0/codebridge/__main__.py +29 -0
- codebridge_mcp-1.0.0/codebridge/qr_generator.py +48 -0
- codebridge_mcp-1.0.0/codebridge/server.py +483 -0
- codebridge_mcp-1.0.0/codebridge/tools.py +143 -0
- codebridge_mcp-1.0.0/codebridge_mcp.egg-info/PKG-INFO +16 -0
- codebridge_mcp-1.0.0/codebridge_mcp.egg-info/SOURCES.txt +11 -0
- codebridge_mcp-1.0.0/codebridge_mcp.egg-info/dependency_links.txt +1 -0
- codebridge_mcp-1.0.0/codebridge_mcp.egg-info/entry_points.txt +2 -0
- codebridge_mcp-1.0.0/codebridge_mcp.egg-info/top_level.txt +1 -0
- codebridge_mcp-1.0.0/pyproject.toml +32 -0
- codebridge_mcp-1.0.0/setup.cfg +4 -0
|
@@ -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 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -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")
|
|
@@ -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()
|
|
@@ -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,11 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
codebridge/__init__.py
|
|
3
|
+
codebridge/__main__.py
|
|
4
|
+
codebridge/qr_generator.py
|
|
5
|
+
codebridge/server.py
|
|
6
|
+
codebridge/tools.py
|
|
7
|
+
codebridge_mcp.egg-info/PKG-INFO
|
|
8
|
+
codebridge_mcp.egg-info/SOURCES.txt
|
|
9
|
+
codebridge_mcp.egg-info/dependency_links.txt
|
|
10
|
+
codebridge_mcp.egg-info/entry_points.txt
|
|
11
|
+
codebridge_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
codebridge
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codebridge-mcp"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "CodeBridge by Bunchhh - Connect your project to any AI. Read, fix, build — no copy-paste. Free AI-powered development."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
keywords = ["ai", "mcp", "claude", "chatgpt", "gemini", "developer-tools", "code-assistant", "bunchhh"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
]
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
codebridge = "codebridge.__main__:main"
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://bunchhh.com"
|
|
26
|
+
GitHub = "https://github.com/bunchhh/codebridge"
|
|
27
|
+
Issues = "https://github.com/bunchhh/codebridge/issues"
|
|
28
|
+
Website = "https://bunchhh.com"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["."]
|
|
32
|
+
include = ["codebridge*"]
|