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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: browse_code
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: A local AI bridge package
5
5
  Requires-Dist: fastapi
6
6
  Requires-Dist: uvicorn
@@ -1,17 +1,18 @@
1
1
  # Browse Code
2
2
 
3
+ ![PyPI version](https://badge.fury.io/py/browse-code.svg)
3
4
  ![Python Version](https://img.shields.io/badge/python-3.8%2B-blue)
4
5
  ![License](https://img.shields.io/badge/license-MIT-green)
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
- ## Features
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
- ## 🚀 Installation
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
- ## 💻 Usage
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
- ## 🤝 Contributing
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
- ## 📄 License
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest_version": 3,
3
- "name": "Local Agent Bridge",
3
+ "name": "Browse Code Bridge",
4
4
  "version": "2.0",
5
5
  "description": "Autonomous AI Code Execution Bridge",
6
6
  "permissions": [
@@ -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 = 'Saved!';
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 = 'Error: Refresh Tab First!';
41
- setTimeout(() => initBtn.innerText = 'Initialize Agent in Chat', 3000);
40
+ initBtn.innerText = 'Error: Refresh Tab First!';
41
+ setTimeout(() => initBtn.innerText = 'Initialize Agent in Chat', 3000);
42
42
  } else {
43
- initBtn.innerText = 'Initialize Agent in Chat';
43
+ initBtn.innerText = 'Initialize Agent in Chat';
44
44
  }
45
45
  });
46
46
  } else {
47
- initBtn.innerText = 'Not a valid AI chat tab';
48
- setTimeout(() => initBtn.innerText = 'Initialize Agent in Chat', 3000);
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 saveWorkspace(path) {
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 updateStatus() {
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
- if request.url.path != "/extension/run-tool":
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: browse_code
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: A local AI bridge package
5
5
  Requires-Dist: fastapi
6
6
  Requires-Dist: uvicorn
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="browse_code",
5
- version="0.1.0",
5
+ version="0.1.1",
6
6
  description="A local AI bridge package",
7
7
  packages=find_packages(),
8
8
  include_package_data=True,
@@ -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