browse-code 0.1.0__tar.gz → 0.1.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.
- {browse_code-0.1.0 → browse_code-0.1.1}/PKG-INFO +1 -1
- {browse_code-0.1.0 → browse_code-0.1.1}/README.md +16 -8
- browse_code-0.1.1/browse_code/__init__.py +1 -0
- browse_code-0.1.1/browse_code/cli.py +133 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code/extension/content.js +7 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code/extension/manifest.json +1 -1
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code/extension/popup.js +8 -8
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code/server.py +25 -1
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code.egg-info/PKG-INFO +1 -1
- {browse_code-0.1.0 → browse_code-0.1.1}/setup.py +1 -1
- browse_code-0.1.0/browse_code/__init__.py +0 -1
- browse_code-0.1.0/browse_code/cli.py +0 -75
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code/extension/popup.html +0 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code/extension/spoof.js +0 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code.egg-info/SOURCES.txt +0 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code.egg-info/dependency_links.txt +0 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code.egg-info/entry_points.txt +0 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code.egg-info/requires.txt +0 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/browse_code.egg-info/top_level.txt +0 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/pyproject.toml +0 -0
- {browse_code-0.1.0 → browse_code-0.1.1}/setup.cfg +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# Browse Code
|
|
2
2
|
|
|
3
|
+

|
|
3
4
|

|
|
4
5
|

|
|
5
6
|
|
|
6
7
|
**Browse Code** is a powerful CLI tool that bridges your local terminal environment with your browser. It runs a local backend server and injects an AI-assisted Chrome extension, enabling seamless automation, local file reads/writes, and background process management directly from the browser.
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## Features
|
|
9
10
|
|
|
10
11
|
- **Local AI Bridge Server:** A fast, secure FastAPI-based server that exposes your local environment (reading files, listing directories, executing terminal commands, managing background processes).
|
|
11
12
|
- **Chrome Extension Integration:** Automatically sets up (with your permission) a Chrome extension that interacts with the backend.
|
|
12
13
|
- **Easy-to-use CLI:** Start your AI sessions with a single `bc` command.
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Installation
|
|
15
16
|
|
|
16
17
|
### For Users
|
|
17
18
|
|
|
@@ -20,7 +21,6 @@ Install the package directly via pip:
|
|
|
20
21
|
```bash
|
|
21
22
|
pip install browse-code
|
|
22
23
|
```
|
|
23
|
-
*(Note: If this package is published to PyPI, you can install it as above. Otherwise, clone the repo and see "For Developers" below).*
|
|
24
24
|
|
|
25
25
|
### For Developers
|
|
26
26
|
|
|
@@ -34,7 +34,7 @@ pip install browse-code
|
|
|
34
34
|
pip install -e .
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## Usage
|
|
38
38
|
|
|
39
39
|
Once installed, simply run the CLI command:
|
|
40
40
|
|
|
@@ -43,10 +43,18 @@ bc
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
### What happens when you run `bc`?
|
|
46
|
-
1. **Extension Setup:** The tool will ask if you want to install its companion Chrome extension. If you agree, it will extract the extension to your home folder (`~/.browse_code/extension`) and offer to launch Chrome with the extension loaded.
|
|
47
|
-
2. **Server Initialization:** It will start the AI session backend server on `http://127.0.0.1:5505`. Keep this terminal open to maintain the bridge connection and view real-time execution logs and diffs.
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
**First run:**
|
|
48
|
+
1. The CLI asks if you want to set up the Chrome extension.
|
|
49
|
+
2. If you agree, it extracts the extension to `~/.browse_code/extension/` and copies the path to your clipboard.
|
|
50
|
+
3. It opens `chrome://extensions/` automatically.
|
|
51
|
+
4. You just need to enable "Developer mode", click "Load unpacked", and paste the path.
|
|
52
|
+
5. Once done, press Enter and the server starts.
|
|
53
|
+
|
|
54
|
+
**Every run after that:**
|
|
55
|
+
The server starts immediately with no prompts. The extension stays loaded in Chrome across sessions.
|
|
56
|
+
|
|
57
|
+
## Contributing
|
|
50
58
|
|
|
51
59
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
52
60
|
|
|
@@ -56,6 +64,6 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
56
64
|
4. Push to the branch (`git push origin feature/AmazingFeature`).
|
|
57
65
|
5. Open a Pull Request.
|
|
58
66
|
|
|
59
|
-
##
|
|
67
|
+
## License
|
|
60
68
|
|
|
61
69
|
Distributed under the MIT License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import shutil
|
|
4
|
+
import platform
|
|
5
|
+
import subprocess
|
|
6
|
+
import webbrowser
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_data_dir():
|
|
11
|
+
"""Return ~/.browse_code, creating it if needed."""
|
|
12
|
+
data_dir = Path.home() / ".browse_code"
|
|
13
|
+
data_dir.mkdir(exist_ok=True)
|
|
14
|
+
return data_dir
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def copy_to_clipboard(text):
|
|
18
|
+
"""Copy text to the system clipboard (cross-platform)."""
|
|
19
|
+
try:
|
|
20
|
+
system = platform.system()
|
|
21
|
+
if system == "Windows":
|
|
22
|
+
subprocess.run("clip", input=text.encode("utf-8"), check=True)
|
|
23
|
+
elif system == "Darwin":
|
|
24
|
+
subprocess.run("pbcopy", input=text.encode("utf-8"), check=True)
|
|
25
|
+
else:
|
|
26
|
+
# Linux — try xclip, fall back to xsel
|
|
27
|
+
try:
|
|
28
|
+
subprocess.run(
|
|
29
|
+
["xclip", "-selection", "clipboard"],
|
|
30
|
+
input=text.encode("utf-8"),
|
|
31
|
+
check=True,
|
|
32
|
+
)
|
|
33
|
+
except FileNotFoundError:
|
|
34
|
+
subprocess.run(
|
|
35
|
+
["xsel", "--clipboard", "--input"],
|
|
36
|
+
input=text.encode("utf-8"),
|
|
37
|
+
check=True,
|
|
38
|
+
)
|
|
39
|
+
return True
|
|
40
|
+
except Exception:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def setup_extension():
|
|
45
|
+
"""First-time setup: extract extension, open Chrome, guide the user."""
|
|
46
|
+
data_dir = get_data_dir()
|
|
47
|
+
ext_dest = data_dir / "extension"
|
|
48
|
+
pkg_ext_dir = Path(__file__).parent / "extension"
|
|
49
|
+
|
|
50
|
+
print()
|
|
51
|
+
print("=" * 60)
|
|
52
|
+
print(" Browse Code - First Time Setup")
|
|
53
|
+
print("=" * 60)
|
|
54
|
+
print()
|
|
55
|
+
print("Browse Code needs a Chrome extension to connect your")
|
|
56
|
+
print("browser to the local server.")
|
|
57
|
+
print()
|
|
58
|
+
|
|
59
|
+
ans = input("Do you want to set up the extension now? (y/n): ")
|
|
60
|
+
if ans.strip().lower() != "y":
|
|
61
|
+
print("Skipped. You can re-run 'bc' anytime to set it up.")
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
# Extract extension files
|
|
65
|
+
print()
|
|
66
|
+
print("Extracting extension files...")
|
|
67
|
+
if ext_dest.exists():
|
|
68
|
+
shutil.rmtree(ext_dest)
|
|
69
|
+
shutil.copytree(pkg_ext_dir, ext_dest)
|
|
70
|
+
|
|
71
|
+
ext_path_str = str(ext_dest)
|
|
72
|
+
print(f" Extension extracted to: {ext_path_str}")
|
|
73
|
+
print()
|
|
74
|
+
|
|
75
|
+
# Copy path to clipboard
|
|
76
|
+
copied = copy_to_clipboard(ext_path_str)
|
|
77
|
+
|
|
78
|
+
# Open chrome://extensions
|
|
79
|
+
print("Opening Chrome extensions page...")
|
|
80
|
+
webbrowser.open("chrome://extensions/")
|
|
81
|
+
|
|
82
|
+
print()
|
|
83
|
+
print("-" * 60)
|
|
84
|
+
print(" QUICK SETUP (one-time, takes 30 seconds):")
|
|
85
|
+
print("-" * 60)
|
|
86
|
+
print(' 1. Enable "Developer mode" toggle (top-right corner)')
|
|
87
|
+
print(' 2. Click "Load unpacked"')
|
|
88
|
+
if copied:
|
|
89
|
+
print(" 3. Paste the path (already copied to your clipboard)")
|
|
90
|
+
else:
|
|
91
|
+
print(f" 3. Select this folder: {ext_path_str}")
|
|
92
|
+
print("-" * 60)
|
|
93
|
+
print()
|
|
94
|
+
|
|
95
|
+
input("Press Enter after you've loaded the extension...")
|
|
96
|
+
|
|
97
|
+
# Mark as installed
|
|
98
|
+
marker = data_dir / ".installed"
|
|
99
|
+
marker.touch()
|
|
100
|
+
|
|
101
|
+
print()
|
|
102
|
+
print("Extension setup complete!")
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main():
|
|
107
|
+
data_dir = get_data_dir()
|
|
108
|
+
marker = data_dir / ".installed"
|
|
109
|
+
|
|
110
|
+
if not marker.exists():
|
|
111
|
+
result = setup_extension()
|
|
112
|
+
if not result:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
print()
|
|
116
|
+
print("=" * 60)
|
|
117
|
+
print(" Browse Code")
|
|
118
|
+
print("=" * 60)
|
|
119
|
+
print()
|
|
120
|
+
print("Starting server...")
|
|
121
|
+
print()
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
from .server import app
|
|
125
|
+
except ImportError:
|
|
126
|
+
from server import app
|
|
127
|
+
|
|
128
|
+
import uvicorn
|
|
129
|
+
uvicorn.run(app, host="127.0.0.1", port=5505, access_log=False)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
main()
|
|
@@ -38,6 +38,13 @@ spoofScript.src = chrome.runtime.getURL('spoof.js');
|
|
|
38
38
|
(document.head || document.documentElement).appendChild(spoofScript);
|
|
39
39
|
spoofScript.onload = function () { this.remove(); };
|
|
40
40
|
|
|
41
|
+
// Heartbeat: ping the server every 5 seconds so it knows the extension is connected
|
|
42
|
+
function pingServer() {
|
|
43
|
+
fetch(`${LOCAL_SERVER}/extension/ping`).catch(() => {});
|
|
44
|
+
}
|
|
45
|
+
pingServer();
|
|
46
|
+
setInterval(pingServer, 5000);
|
|
47
|
+
|
|
41
48
|
const SYSTEM_PROMPT = `You are an elite, autonomous AI software engineer (similar to Antigravity or Devin) connected to a local execution bridge.
|
|
42
49
|
You do NOT have direct access to my file system. You interact with it ONLY by emitting the XML tool tags defined below, which are executed locally on my machine.
|
|
43
50
|
|
|
@@ -24,7 +24,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
24
24
|
}
|
|
25
25
|
chrome.storage.local.set({ workspaceDir: path, injectN: injectN });
|
|
26
26
|
syncWithServer(path);
|
|
27
|
-
saveBtn.innerText = '
|
|
27
|
+
saveBtn.innerText = 'Saved!';
|
|
28
28
|
setTimeout(() => saveBtn.innerText = 'Save Directory', 2000);
|
|
29
29
|
});
|
|
30
30
|
|
|
@@ -37,20 +37,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
37
37
|
if (activeTab && validUrls.some(url => activeTab.url.includes(url))) {
|
|
38
38
|
chrome.tabs.sendMessage(activeTab.id, { action: "INITIALIZE_AGENT" }, () => {
|
|
39
39
|
if (chrome.runtime.lastError) {
|
|
40
|
-
initBtn.innerText = '
|
|
41
|
-
setTimeout(() => initBtn.innerText = '
|
|
40
|
+
initBtn.innerText = 'Error: Refresh Tab First!';
|
|
41
|
+
setTimeout(() => initBtn.innerText = 'Initialize Agent in Chat', 3000);
|
|
42
42
|
} else {
|
|
43
|
-
initBtn.innerText = '
|
|
43
|
+
initBtn.innerText = 'Initialize Agent in Chat';
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
} else {
|
|
47
|
-
initBtn.innerText = '
|
|
48
|
-
setTimeout(() => initBtn.innerText = '
|
|
47
|
+
initBtn.innerText = 'Not a valid AI chat tab';
|
|
48
|
+
setTimeout(() => initBtn.innerText = 'Initialize Agent in Chat', 3000);
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
function
|
|
53
|
+
function syncWithServer(path) {
|
|
54
54
|
fetch('http://127.0.0.1:5505/set-workspace', {
|
|
55
55
|
method: 'POST',
|
|
56
56
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -60,7 +60,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function
|
|
63
|
+
function pollStatus() {
|
|
64
64
|
fetch('http://127.0.0.1:5505/status')
|
|
65
65
|
.then(res => res.json())
|
|
66
66
|
.then(data => {
|
|
@@ -114,6 +114,11 @@ def print_startup_banner(workspace: str, host: str, port: int):
|
|
|
114
114
|
ep_text = f" Endpoint http://{host}:{port}"
|
|
115
115
|
ep_pad = max(0, width - len(ep_text))
|
|
116
116
|
print(f"{C_HEADER}│{C_RESET} {C_MUTED}Endpoint {C_RESET} http://{host}:{port}{' ' * ep_pad}{C_HEADER}│{C_RESET}")
|
|
117
|
+
ext_label = "Extension"
|
|
118
|
+
ext_status = "Waiting for connection..."
|
|
119
|
+
ext_text = f" {ext_label} {ext_status}"
|
|
120
|
+
ext_pad = max(0, width - len(ext_text))
|
|
121
|
+
print(f"{C_HEADER}│{C_RESET} {C_MUTED}{ext_label}{C_RESET} {C_WARN}{ext_status}{C_RESET}{' ' * ext_pad}{C_HEADER}│{C_RESET}")
|
|
117
122
|
print(f"{C_HEADER}╰{'─' * width}╯{C_RESET}")
|
|
118
123
|
print()
|
|
119
124
|
|
|
@@ -121,6 +126,24 @@ def print_startup_banner(workspace: str, host: str, port: int):
|
|
|
121
126
|
WORKSPACE_DIR = r"C:\Users\dedeep vasireddy\Downloads\test"
|
|
122
127
|
BACKGROUND_PROCESSES = {}
|
|
123
128
|
|
|
129
|
+
# Extension heartbeat tracking
|
|
130
|
+
_last_extension_ping = None
|
|
131
|
+
_HEARTBEAT_TIMEOUT = 10 # seconds — extension is "connected" if pinged within this window
|
|
132
|
+
|
|
133
|
+
def is_extension_connected():
|
|
134
|
+
if _last_extension_ping is None:
|
|
135
|
+
return False
|
|
136
|
+
return (time.time() - _last_extension_ping) < _HEARTBEAT_TIMEOUT
|
|
137
|
+
|
|
138
|
+
@app.get("/extension/ping")
|
|
139
|
+
async def extension_ping():
|
|
140
|
+
global _last_extension_ping
|
|
141
|
+
was_connected = is_extension_connected()
|
|
142
|
+
_last_extension_ping = time.time()
|
|
143
|
+
if not was_connected:
|
|
144
|
+
print(f"\n{C_OK}[+] Extension connected{C_RESET}")
|
|
145
|
+
return {"status": "ok"}
|
|
146
|
+
|
|
124
147
|
def cleanup_background_processes():
|
|
125
148
|
print(f"\n{C_WARN}⏻ Shutting down — cleaning up background processes...{C_RESET}")
|
|
126
149
|
for pid, data in BACKGROUND_PROCESSES.items():
|
|
@@ -150,7 +173,8 @@ async def request_logger(request: Request, call_next):
|
|
|
150
173
|
|
|
151
174
|
# The /extension/run-tool endpoint already prints its own detailed
|
|
152
175
|
# banner + result, so skip the generic line there to avoid duplication.
|
|
153
|
-
|
|
176
|
+
# Also skip /extension/ping to avoid flooding the log with heartbeats.
|
|
177
|
+
if request.url.path not in ("/extension/run-tool", "/extension/ping"):
|
|
154
178
|
print(f"{C_DIM}→{C_RESET} {request.method} {request.url.path} "
|
|
155
179
|
f"{color}{status}{C_RESET} {C_DIM}{elapsed_ms:.0f}ms{C_RESET}")
|
|
156
180
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
import shutil
|
|
4
|
-
import subprocess
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
def get_data_dir():
|
|
8
|
-
home = Path.home()
|
|
9
|
-
data_dir = home / ".browse_code"
|
|
10
|
-
data_dir.mkdir(exist_ok=True)
|
|
11
|
-
return data_dir
|
|
12
|
-
|
|
13
|
-
def install_extension():
|
|
14
|
-
data_dir = get_data_dir()
|
|
15
|
-
installed_marker = data_dir / ".installed"
|
|
16
|
-
ext_dest = data_dir / "extension"
|
|
17
|
-
|
|
18
|
-
# Copy extension files from package to ~/.browse_code/extension
|
|
19
|
-
pkg_ext_dir = Path(__file__).parent / "extension"
|
|
20
|
-
|
|
21
|
-
if not installed_marker.exists():
|
|
22
|
-
ans_allow = input("Do you allow browse_code to install its Chrome extension? (y/n): ")
|
|
23
|
-
if ans_allow.lower() != 'y':
|
|
24
|
-
print("Extension installation skipped.")
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
print("Installing browse_code extension...")
|
|
28
|
-
if ext_dest.exists():
|
|
29
|
-
shutil.rmtree(ext_dest)
|
|
30
|
-
shutil.copytree(pkg_ext_dir, ext_dest)
|
|
31
|
-
|
|
32
|
-
print("\n========================================================")
|
|
33
|
-
print("Extension files copied to: ", ext_dest)
|
|
34
|
-
print("To load the extension in Chrome:")
|
|
35
|
-
print("1. Open Chrome and navigate to chrome://extensions/")
|
|
36
|
-
print("2. Enable 'Developer mode' at the top right")
|
|
37
|
-
print("3. Click 'Load unpacked' and select the folder:")
|
|
38
|
-
print(f" {ext_dest}")
|
|
39
|
-
print("========================================================\n")
|
|
40
|
-
|
|
41
|
-
ans = input("Do you want me to try launching Chrome with this extension now? (y/n): ")
|
|
42
|
-
if ans.lower() == 'y':
|
|
43
|
-
# Attempt to launch chrome
|
|
44
|
-
# On Windows:
|
|
45
|
-
chrome_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
|
|
46
|
-
if not os.path.exists(chrome_path):
|
|
47
|
-
chrome_path = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
|
|
48
|
-
if os.path.exists(chrome_path):
|
|
49
|
-
subprocess.Popen([chrome_path, f"--load-extension={ext_dest}"])
|
|
50
|
-
else:
|
|
51
|
-
print("Could not find Chrome executable automatically.")
|
|
52
|
-
|
|
53
|
-
installed_marker.touch()
|
|
54
|
-
else:
|
|
55
|
-
# Already installed, check if files are still there, if not recopy
|
|
56
|
-
if not ext_dest.exists():
|
|
57
|
-
shutil.copytree(pkg_ext_dir, ext_dest)
|
|
58
|
-
|
|
59
|
-
def main():
|
|
60
|
-
install_extension()
|
|
61
|
-
|
|
62
|
-
print("\nStarting AI session server...\n")
|
|
63
|
-
# Now we need to start the FastAPI server.
|
|
64
|
-
# The original server.py is in the same package
|
|
65
|
-
try:
|
|
66
|
-
from .server import app
|
|
67
|
-
except ImportError:
|
|
68
|
-
# Fallback if run directly during dev
|
|
69
|
-
from server import app
|
|
70
|
-
|
|
71
|
-
import uvicorn
|
|
72
|
-
uvicorn.run(app, host="127.0.0.1", port=5505, access_log=False)
|
|
73
|
-
|
|
74
|
-
if __name__ == "__main__":
|
|
75
|
-
main()
|
|
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
|