browse-code 0.1.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.
- browse_code-0.1.0/PKG-INFO +9 -0
- browse_code-0.1.0/README.md +61 -0
- browse_code-0.1.0/browse_code/__init__.py +1 -0
- browse_code-0.1.0/browse_code/cli.py +75 -0
- browse_code-0.1.0/browse_code/extension/content.js +356 -0
- browse_code-0.1.0/browse_code/extension/manifest.json +30 -0
- browse_code-0.1.0/browse_code/extension/popup.html +109 -0
- browse_code-0.1.0/browse_code/extension/popup.js +90 -0
- browse_code-0.1.0/browse_code/extension/spoof.js +3 -0
- browse_code-0.1.0/browse_code/server.py +574 -0
- browse_code-0.1.0/browse_code.egg-info/PKG-INFO +9 -0
- browse_code-0.1.0/browse_code.egg-info/SOURCES.txt +17 -0
- browse_code-0.1.0/browse_code.egg-info/dependency_links.txt +1 -0
- browse_code-0.1.0/browse_code.egg-info/entry_points.txt +2 -0
- browse_code-0.1.0/browse_code.egg-info/requires.txt +3 -0
- browse_code-0.1.0/browse_code.egg-info/top_level.txt +1 -0
- browse_code-0.1.0/pyproject.toml +3 -0
- browse_code-0.1.0/setup.cfg +4 -0
- browse_code-0.1.0/setup.py +22 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Browse Code
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
**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
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- **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
|
+
- **Chrome Extension Integration:** Automatically sets up (with your permission) a Chrome extension that interacts with the backend.
|
|
12
|
+
- **Easy-to-use CLI:** Start your AI sessions with a single `bc` command.
|
|
13
|
+
|
|
14
|
+
## 🚀 Installation
|
|
15
|
+
|
|
16
|
+
### For Users
|
|
17
|
+
|
|
18
|
+
Install the package directly via pip:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install browse-code
|
|
22
|
+
```
|
|
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
|
+
|
|
25
|
+
### For Developers
|
|
26
|
+
|
|
27
|
+
1. Clone the repository:
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/Dedeep007/browse_code.git
|
|
30
|
+
cd browse_code
|
|
31
|
+
```
|
|
32
|
+
2. Install in editable mode:
|
|
33
|
+
```bash
|
|
34
|
+
pip install -e .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 💻 Usage
|
|
38
|
+
|
|
39
|
+
Once installed, simply run the CLI command:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bc
|
|
43
|
+
```
|
|
44
|
+
|
|
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
|
+
|
|
49
|
+
## 🤝 Contributing
|
|
50
|
+
|
|
51
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
52
|
+
|
|
53
|
+
1. Fork the repository.
|
|
54
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
|
|
55
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
|
|
56
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`).
|
|
57
|
+
5. Open a Pull Request.
|
|
58
|
+
|
|
59
|
+
## 📄 License
|
|
60
|
+
|
|
61
|
+
Distributed under the MIT License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,75 @@
|
|
|
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()
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
const LOCAL_SERVER = "http://127.0.0.1:5505";
|
|
2
|
+
const hostname = window.location.hostname;
|
|
3
|
+
|
|
4
|
+
let PLATFORM = {};
|
|
5
|
+
|
|
6
|
+
if (hostname.includes('gemini.google.com')) {
|
|
7
|
+
PLATFORM = {
|
|
8
|
+
name: "Gemini",
|
|
9
|
+
inputBox: 'rich-textarea div[contenteditable="true"], div[role="textbox"][contenteditable="true"]',
|
|
10
|
+
sendBtn: 'button[aria-label*="Send"], button[aria-label*="send"], button[mattooltip*="Send"]',
|
|
11
|
+
responseContainer: 'message-content, model-response, .model-response-text'
|
|
12
|
+
};
|
|
13
|
+
} else if (hostname.includes('claude.ai')) {
|
|
14
|
+
PLATFORM = {
|
|
15
|
+
name: "Claude",
|
|
16
|
+
inputBox: '.ProseMirror[contenteditable="true"]',
|
|
17
|
+
sendBtn: 'button[aria-label*="Send"], button[aria-label*="send"]',
|
|
18
|
+
responseContainer: '.font-claude-response, .font-claude-message'
|
|
19
|
+
};
|
|
20
|
+
} else if (hostname.includes('huggingface.co')) {
|
|
21
|
+
PLATFORM = {
|
|
22
|
+
name: "HuggingFace",
|
|
23
|
+
inputBox: 'input[name="prompt"]',
|
|
24
|
+
sendBtn: 'button[type="submit"]',
|
|
25
|
+
responseContainer: '.prose, .markdown, .break-words, [class*="message"]'
|
|
26
|
+
};
|
|
27
|
+
} else {
|
|
28
|
+
PLATFORM = {
|
|
29
|
+
name: "ChatGPT",
|
|
30
|
+
inputBox: '#prompt-textarea',
|
|
31
|
+
sendBtn: '[data-testid="send-button"], button[data-testid*="send"]',
|
|
32
|
+
responseContainer: '.markdown, .prose'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const spoofScript = document.createElement('script');
|
|
37
|
+
spoofScript.src = chrome.runtime.getURL('spoof.js');
|
|
38
|
+
(document.head || document.documentElement).appendChild(spoofScript);
|
|
39
|
+
spoofScript.onload = function () { this.remove(); };
|
|
40
|
+
|
|
41
|
+
const SYSTEM_PROMPT = `You are an elite, autonomous AI software engineer (similar to Antigravity or Devin) connected to a local execution bridge.
|
|
42
|
+
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
|
+
|
|
44
|
+
═══════════════════════════════════════
|
|
45
|
+
GOLDEN RULES (violating any of these breaks the session)
|
|
46
|
+
═══════════════════════════════════════
|
|
47
|
+
1. STOP AFTER EVERY TOOL CALL. The moment you emit one or more tool tags, END YOUR TURN. Do not write "Result:", do not guess what the output will be, do not continue coding as if you already have it. A human/script will run the tool and give you the real output in the next message. Generating fake tool output is the single worst failure mode — never do it.
|
|
48
|
+
2. NEVER claim a file was created, edited, or tested unless you emitted the corresponding tag THIS turn AND already received its result.
|
|
49
|
+
3. NEVER fabricate file contents, line numbers, search results, or terminal output. If you haven't seen it via a tool result in this conversation, you don't know it.
|
|
50
|
+
4. Explore before you edit. Default order: view_dir → search_code → read_lines → patch/write. Do not read a full file "just in case" — read only what your search told you to read.
|
|
51
|
+
5. Prefer patch over write. Only use write for brand-new files or a full intentional rewrite. patch requires the <search> block to match the existing file EXACTLY (same whitespace/indentation) — read_lines first if you're not certain of the exact text.
|
|
52
|
+
6. Terminals have no memory of directory state between calls. Every terminal_run/terminal_bg must include any necessary cd, chained with &&.
|
|
53
|
+
7. **terminal_run is FULLY BLOCKING and HARD-CAPPED at 300s.** Under the hood it runs synchronously and only returns once the process exits completely — there is no streaming, no partial progress, nothing, until it's done or the 300s timeout fires. If it times out, you get a forced error containing only whatever STDOUT/STDERR happened to be captured before the kill — this is NOT a reliable signal of success or failure, just a fragment.
|
|
54
|
+
Because of this, terminal_run must NEVER be used for anything that installs dependencies, builds, scaffolds a project, or could plausibly run past a few seconds — e.g. npm/yarn/pnpm install, pip install, create-next-app, docker build, webpack/vite build, test suites with network calls, git clone of large repos, etc.
|
|
55
|
+
For ALL such commands: start with terminal_bg (returns a PID instantly, non-blocking), then poll with cron_monitor (delay 20-30s) — repeat cron_monitor while status is still "running." Only treat the command as done when a poll returns a completed/exited status.
|
|
56
|
+
If you ever see a terminal_run timeout error, do NOT re-run the same command with terminal_run again — restart it via terminal_bg instead.
|
|
57
|
+
terminal_run is only appropriate for short, fast, deterministic commands you're confident finish in a few seconds (e.g. ls, cat, a single quick lint check, git status).
|
|
58
|
+
8. If a result is [TRUNCATED] or a file is large, narrow down with search_code or read_lines instead of re-requesting the whole file.
|
|
59
|
+
|
|
60
|
+
═══════════════════════════════════════
|
|
61
|
+
WORKFLOW: "Autonomous Researcher"
|
|
62
|
+
═══════════════════════════════════════
|
|
63
|
+
1. **Explore:** view_dir to understand project structure.
|
|
64
|
+
2. **Search:** search_code to grep for exactly which file/line defines a function or variable.
|
|
65
|
+
3. **Inspect:** read_lines to read only the relevant chunk.
|
|
66
|
+
4. **Modify:** patch to surgically replace targeted code. write only for brand-new files.
|
|
67
|
+
5. **Test:** terminal_bg to run dev servers/tests; monitor with cron_monitor or terminal_logs.
|
|
68
|
+
|
|
69
|
+
═══════════════════════════════════════
|
|
70
|
+
AVAILABLE TOOLS
|
|
71
|
+
═══════════════════════════════════════
|
|
72
|
+
CRITICAL FORMAT RULE: Every individual tool call is wrapped in its OWN \`\`\`tool ... \`\`\` fence. If you issue multiple tool calls in one turn, output multiple separate \`\`\`tool\`\`\` blocks back to back — never combine two tags inside one fence.
|
|
73
|
+
|
|
74
|
+
| Tool | Format | Description |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| **View Dir** | \`\`\`tool\n<tool='view_dir'>src/components</tool>\n\`\`\` | Lists files. Leave empty for root. |
|
|
77
|
+
| **Search Code** | \`\`\`tool\n<tool='search_code' query='functionName'>src</tool>\n\`\`\` | Fast regex/string search. Returns file & line numbers. |
|
|
78
|
+
| **Read Lines** | \`\`\`tool\n<tool='read_lines' path='App.js' start='20' end='45'></tool>\n\`\`\` | Reads a specific chunk of a file. |
|
|
79
|
+
| **Read File** | \`\`\`tool\n<tool='read'>path/to/file.py</tool>\n\`\`\` | Reads a full file (avoid if file is large — prefer search_code + read_lines). |
|
|
80
|
+
| **Write File** | \`\`\`tool\n<tool='write' path='path.js'>\nRAW CODE\n</tool>\n\`\`\` | Creates/overwrites a file completely. |
|
|
81
|
+
| **Patch Code** | \`\`\`tool\n<tool='patch' path='main.py'>\n<search>\nOLD\n</search>\n<replace>\nNEW\n</replace>\n</tool>\n\`\`\` | Surgically replaces a block. <search> must match the file byte-for-byte. |
|
|
82
|
+
| **Run Terminal** | \`\`\`tool\n<tool='terminal_run'>npm test</tool>\n\`\`\` | BLOCKING, synchronous, hard 300s timeout. Only for short/fast commands (see Rule 7). |
|
|
83
|
+
| **Background Term**| \`\`\`tool\n<tool='terminal_bg'>npm run dev</tool>\n\`\`\` | Non-blocking. Returns a PID instantly. Required for installs/builds/servers. |
|
|
84
|
+
| **Cron Monitor** | \`\`\`tool\n<tool='cron_monitor' pid='1234' delay='15'></tool>\n\`\`\` | Sleeps X seconds, then returns the background PID's logs. Poll repeatedly until status is no longer "running." |
|
|
85
|
+
| **Term Logs** | \`\`\`tool\n<tool='terminal_logs' pid='1234'></tool>\n\`\`\` | Reads output of a background PID instantly. |
|
|
86
|
+
| **Kill Term** | \`\`\`tool\n<tool='terminal_kill' pid='1234'></tool>\n\`\`\` | Stops a background PID. |
|
|
87
|
+
|
|
88
|
+
═══════════════════════════════════════
|
|
89
|
+
WORKED EXAMPLES
|
|
90
|
+
═══════════════════════════════════════
|
|
91
|
+
|
|
92
|
+
--- Example A: code fix (stop-and-wait pattern) ---
|
|
93
|
+
User: "Fix the off-by-one in the pagination helper."
|
|
94
|
+
|
|
95
|
+
Your turn:
|
|
96
|
+
I'll locate the pagination logic first.
|
|
97
|
+
\`\`\`tool
|
|
98
|
+
<tool='search_code' query='pagination'>src</tool>
|
|
99
|
+
\`\`\`
|
|
100
|
+
[END TURN — wait for result]
|
|
101
|
+
|
|
102
|
+
Next turn (after receiving real results):
|
|
103
|
+
Found it in src/utils/paginate.js:14.
|
|
104
|
+
\`\`\`tool
|
|
105
|
+
<tool='read_lines' path='src/utils/paginate.js' start='1' end='30'></tool>
|
|
106
|
+
\`\`\`
|
|
107
|
+
[END TURN — wait for result]
|
|
108
|
+
|
|
109
|
+
Next turn (after receiving the real file contents):
|
|
110
|
+
\`\`\`tool
|
|
111
|
+
<tool='patch' path='src/utils/paginate.js'>
|
|
112
|
+
<search>
|
|
113
|
+
return start + pageSize;
|
|
114
|
+
</search>
|
|
115
|
+
<replace>
|
|
116
|
+
return start + pageSize - 1;
|
|
117
|
+
</replace>
|
|
118
|
+
</tool>
|
|
119
|
+
\`\`\`
|
|
120
|
+
[END TURN — wait for result. Only say "Fixed" after this result confirms success.]
|
|
121
|
+
|
|
122
|
+
--- Example B: long-running install (bg + poll pattern) ---
|
|
123
|
+
User: "Scaffold a new Next.js app called 'dashboard'."
|
|
124
|
+
|
|
125
|
+
Your turn:
|
|
126
|
+
This will download packages, so I'll run it in the background.
|
|
127
|
+
\`\`\`tool
|
|
128
|
+
<tool='terminal_bg'>npx create-next-app@latest dashboard --yes</tool>
|
|
129
|
+
\`\`\`
|
|
130
|
+
[END TURN — wait for PID]
|
|
131
|
+
|
|
132
|
+
Next turn (after receiving PID, e.g. 4821):
|
|
133
|
+
\`\`\`tool
|
|
134
|
+
<tool='cron_monitor' pid='4821' delay='25'></tool>
|
|
135
|
+
\`\`\`
|
|
136
|
+
[END TURN — wait for result]
|
|
137
|
+
|
|
138
|
+
Next turn: if status is still "running," issue cron_monitor again. Only once the log shows completion do you proceed or report success — never use terminal_run for this command, and never assume it finished just because time has passed.
|
|
139
|
+
|
|
140
|
+
═══════════════════════════════════════
|
|
141
|
+
COMPLETION
|
|
142
|
+
═══════════════════════════════════════
|
|
143
|
+
When the task is fully done and you have real tool results confirming every change, reply in plain text summarizing exactly what changed (files touched, what was added/removed/fixed, commands run and their confirmed final status) — no more and no less than what you actually executed and verified this session.
|
|
144
|
+
|
|
145
|
+
To confirm you understand these instructions, reply ONLY with: "Agent Initialized. Awaiting command."`;
|
|
146
|
+
|
|
147
|
+
let messageCount = 0;
|
|
148
|
+
let injectN = 5;
|
|
149
|
+
|
|
150
|
+
chrome.storage.local.get(['injectN'], (res) => {
|
|
151
|
+
if (res.injectN !== undefined) injectN = parseInt(res.injectN, 10);
|
|
152
|
+
});
|
|
153
|
+
chrome.storage.onChanged.addListener((changes, namespace) => {
|
|
154
|
+
if (namespace === 'local' && changes.injectN) {
|
|
155
|
+
injectN = parseInt(changes.injectN.newValue, 10);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
160
|
+
sendResponse({ status: "received" });
|
|
161
|
+
if (request.action === "INITIALIZE_AGENT") {
|
|
162
|
+
messageCount = 0;
|
|
163
|
+
startNewChat()
|
|
164
|
+
.then(() => injectAndSend(SYSTEM_PROMPT))
|
|
165
|
+
.catch(err => console.error("Initialization sequence failed:", err));
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
async function startNewChat() {
|
|
171
|
+
if (PLATFORM.name === "HuggingFace") {
|
|
172
|
+
return Promise.resolve();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (PLATFORM.name === "Gemini") {
|
|
176
|
+
const elements = document.querySelectorAll('span, div, a');
|
|
177
|
+
for (let el of elements) {
|
|
178
|
+
if (el.textContent.trim().toLowerCase() === 'new chat') {
|
|
179
|
+
el.closest('button, a, [role="button"]')?.click();
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else if (PLATFORM.name === "Claude") {
|
|
184
|
+
const newChatBtn = document.querySelector('a[href="/new"], a[href="/chat/new"]');
|
|
185
|
+
if (newChatBtn) newChatBtn.click();
|
|
186
|
+
} else {
|
|
187
|
+
const newChatBtn = document.querySelector('a[href="/"], [data-testid="new-chat-button"]');
|
|
188
|
+
if (newChatBtn) newChatBtn.click();
|
|
189
|
+
}
|
|
190
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function injectAndSend(promptText) {
|
|
194
|
+
let inputBox = null;
|
|
195
|
+
for (let i = 0; i < 30; i++) {
|
|
196
|
+
inputBox = document.querySelector(PLATFORM.inputBox);
|
|
197
|
+
if (inputBox) break;
|
|
198
|
+
await new Promise(r => setTimeout(r, 200));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!inputBox) return;
|
|
202
|
+
inputBox.focus();
|
|
203
|
+
|
|
204
|
+
if (inputBox.tagName === 'INPUT' || inputBox.tagName === 'TEXTAREA') {
|
|
205
|
+
inputBox.value = promptText;
|
|
206
|
+
} else {
|
|
207
|
+
const dataTransfer = new DataTransfer();
|
|
208
|
+
dataTransfer.setData('text/plain', promptText);
|
|
209
|
+
const pasteEvent = new ClipboardEvent('paste', {
|
|
210
|
+
clipboardData: dataTransfer,
|
|
211
|
+
bubbles: true,
|
|
212
|
+
cancelable: true
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
inputBox.dispatchEvent(pasteEvent);
|
|
216
|
+
|
|
217
|
+
if (inputBox.textContent.trim() === "") {
|
|
218
|
+
document.execCommand('insertText', false, promptText);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
inputBox.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
223
|
+
inputBox.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
224
|
+
|
|
225
|
+
await new Promise(r => setTimeout(r, 800));
|
|
226
|
+
|
|
227
|
+
const sendButton = document.querySelector(PLATFORM.sendBtn);
|
|
228
|
+
if (sendButton && (!sendButton.disabled && sendButton.getAttribute('aria-disabled') !== 'true')) {
|
|
229
|
+
sendButton.click();
|
|
230
|
+
} else {
|
|
231
|
+
inputBox.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter' }));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// --- NEW: Robust Text Stability Engine ---
|
|
236
|
+
let isWaitingForLLM = false;
|
|
237
|
+
let lastProcessedMessage = "";
|
|
238
|
+
let trackInterval = null;
|
|
239
|
+
|
|
240
|
+
const messageQueue = [];
|
|
241
|
+
let isInjectingQueue = false;
|
|
242
|
+
|
|
243
|
+
function processQueue() {
|
|
244
|
+
if (messageQueue.length === 0 || isInjectingQueue || isWaitingForLLM) return;
|
|
245
|
+
|
|
246
|
+
let inputBox = document.querySelector(PLATFORM.inputBox);
|
|
247
|
+
if (!inputBox) return;
|
|
248
|
+
|
|
249
|
+
// Do not interrupt the user if they are currently typing a message
|
|
250
|
+
const userText = inputBox.tagName === 'INPUT' || inputBox.tagName === 'TEXTAREA' ? inputBox.value : inputBox.textContent;
|
|
251
|
+
if (userText && userText.trim() !== "") return;
|
|
252
|
+
|
|
253
|
+
// Do not inject if the LLM is currently generating (Send button is missing or disabled)
|
|
254
|
+
const sendButton = document.querySelector(PLATFORM.sendBtn);
|
|
255
|
+
if (!sendButton || sendButton.disabled || sendButton.getAttribute('aria-disabled') === 'true') {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const nextMessage = messageQueue.shift();
|
|
260
|
+
isInjectingQueue = true;
|
|
261
|
+
|
|
262
|
+
injectAndSend(nextMessage).then(() => {
|
|
263
|
+
isInjectingQueue = false;
|
|
264
|
+
}).catch((err) => {
|
|
265
|
+
console.error("Queue injection failed", err);
|
|
266
|
+
isInjectingQueue = false;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Primary observer loop: checks every 1 second
|
|
271
|
+
setInterval(() => {
|
|
272
|
+
processQueue();
|
|
273
|
+
|
|
274
|
+
if (isWaitingForLLM) return;
|
|
275
|
+
|
|
276
|
+
const allContainers = document.querySelectorAll(PLATFORM.responseContainer);
|
|
277
|
+
// Filter out hidden carousel messages
|
|
278
|
+
const containers = Array.from(allContainers).filter(el => el.offsetWidth > 0 && el.offsetHeight > 0);
|
|
279
|
+
if (containers.length === 0) return;
|
|
280
|
+
|
|
281
|
+
const lastContainer = containers[containers.length - 1];
|
|
282
|
+
if (lastContainer.hasAttribute('data-agent-processed')) return;
|
|
283
|
+
|
|
284
|
+
// Grab the very last message in the chat using textContent to avoid layout thrashing
|
|
285
|
+
const currentText = lastContainer.textContent || "";
|
|
286
|
+
|
|
287
|
+
// If the LLM has generated a closing tool tag that we haven't processed yet, lock it in and wait for it to finish typing
|
|
288
|
+
if (currentText.includes("</tool>")) {
|
|
289
|
+
isWaitingForLLM = true;
|
|
290
|
+
trackResponse(currentText);
|
|
291
|
+
}
|
|
292
|
+
}, 1000);
|
|
293
|
+
|
|
294
|
+
function trackResponse(initialText) {
|
|
295
|
+
let previousText = initialText;
|
|
296
|
+
let unchangedTicks = 0;
|
|
297
|
+
|
|
298
|
+
if (trackInterval) clearInterval(trackInterval);
|
|
299
|
+
|
|
300
|
+
// Sub-loop: monitors text generation speed
|
|
301
|
+
trackInterval = setInterval(async () => {
|
|
302
|
+
const allContainers = document.querySelectorAll(PLATFORM.responseContainer);
|
|
303
|
+
const containers = Array.from(allContainers).filter(el => el.offsetWidth > 0 && el.offsetHeight > 0);
|
|
304
|
+
const lastContainer = containers.length > 0 ? containers[containers.length - 1] : null;
|
|
305
|
+
if (!lastContainer) return;
|
|
306
|
+
|
|
307
|
+
const currentText = lastContainer.textContent || "";
|
|
308
|
+
|
|
309
|
+
// Check if the LLM is still typing
|
|
310
|
+
if (currentText.length > previousText.length) {
|
|
311
|
+
previousText = currentText;
|
|
312
|
+
unchangedTicks = 0;
|
|
313
|
+
} else {
|
|
314
|
+
unchangedTicks++;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// If text has not changed for 2.5 seconds (5 ticks), assume generation is complete!
|
|
318
|
+
if (unchangedTicks > 4) {
|
|
319
|
+
clearInterval(trackInterval);
|
|
320
|
+
lastContainer.setAttribute('data-agent-processed', 'true');
|
|
321
|
+
isWaitingForLLM = false; // UNLOCK IMMEDIATELY to prevent deadlocks from long-running tools
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
const toolMatches = currentText.match(/<tool=[\s\S]*?<\/tool>/g);
|
|
325
|
+
if (toolMatches && toolMatches.length > 0) {
|
|
326
|
+
try {
|
|
327
|
+
const toolPromises = toolMatches.map(async (toolCall) => {
|
|
328
|
+
const response = await fetch(`${LOCAL_SERVER}/extension/run-tool`, {
|
|
329
|
+
method: 'POST',
|
|
330
|
+
headers: { 'Content-Type': 'application/json' },
|
|
331
|
+
body: JSON.stringify({ tool_call: toolCall })
|
|
332
|
+
});
|
|
333
|
+
const resultData = await response.json();
|
|
334
|
+
|
|
335
|
+
const toolNameMatch = toolCall.match(/<tool='(.*?)'/);
|
|
336
|
+
const toolName = toolNameMatch ? toolNameMatch[1] : 'unknown';
|
|
337
|
+
|
|
338
|
+
return `\n[System - Tool Execution: ${toolName}]\nStatus: Success\n------------------------------------\nResult Output:\n${resultData.output}\n------------------------------------\n`;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const results = await Promise.all(toolPromises);
|
|
342
|
+
let combinedFeedback = results.join("") + "Instruction: Review the output above. If the task requires more steps, continue. If finished, summarize what was done.";
|
|
343
|
+
|
|
344
|
+
messageCount++;
|
|
345
|
+
if (injectN > 0 && messageCount % injectN === 0) {
|
|
346
|
+
combinedFeedback += `\n\n[System Reminder (Context Refresh)]:\n${SYSTEM_PROMPT}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
messageQueue.push(combinedFeedback);
|
|
350
|
+
} catch (err) {
|
|
351
|
+
messageQueue.push(`\n[System - Error]: Tool execution failed. Ensure your Python backend is running.`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}, 500);
|
|
356
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Local Agent Bridge",
|
|
4
|
+
"version": "2.0",
|
|
5
|
+
"description": "Autonomous AI Code Execution Bridge",
|
|
6
|
+
"permissions": [
|
|
7
|
+
"activeTab",
|
|
8
|
+
"storage"
|
|
9
|
+
],
|
|
10
|
+
"content_scripts": [
|
|
11
|
+
{
|
|
12
|
+
"matches": [
|
|
13
|
+
"*://*.chatgpt.com/*",
|
|
14
|
+
"*://gemini.google.com/*",
|
|
15
|
+
"*://*.claude.ai/*",
|
|
16
|
+
"*://*.huggingface.co/*"
|
|
17
|
+
],
|
|
18
|
+
"js": ["content.js"]
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"action": {
|
|
22
|
+
"default_popup": "popup.html"
|
|
23
|
+
},
|
|
24
|
+
"web_accessible_resources": [
|
|
25
|
+
{
|
|
26
|
+
"resources": ["spoof.js"],
|
|
27
|
+
"matches": ["<all_urls>"]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<style>
|
|
6
|
+
body {
|
|
7
|
+
font-family: 'Segoe UI', sans-serif;
|
|
8
|
+
width: 350px;
|
|
9
|
+
padding: 15px;
|
|
10
|
+
margin: 0;
|
|
11
|
+
background-color: #0f172a;
|
|
12
|
+
color: #f8fafc;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
h3 {
|
|
16
|
+
margin-top: 0;
|
|
17
|
+
font-size: 16px;
|
|
18
|
+
border-bottom: 1px solid #334155;
|
|
19
|
+
padding-bottom: 5px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
label {
|
|
23
|
+
font-size: 12px;
|
|
24
|
+
font-weight: bold;
|
|
25
|
+
color: #94a3b8;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
input {
|
|
29
|
+
width: 100%;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
padding: 8px;
|
|
32
|
+
margin: 8px 0;
|
|
33
|
+
background: #1e293b;
|
|
34
|
+
border: 1px solid #475569;
|
|
35
|
+
color: white;
|
|
36
|
+
border-radius: 4px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
button {
|
|
40
|
+
width: 100%;
|
|
41
|
+
padding: 10px;
|
|
42
|
+
font-weight: bold;
|
|
43
|
+
border: none;
|
|
44
|
+
border-radius: 5px;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
transition: 0.2s;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#save-btn {
|
|
50
|
+
background-color: #3b82f6;
|
|
51
|
+
color: white;
|
|
52
|
+
margin-bottom: 15px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#init-btn {
|
|
56
|
+
background-color: #10b981;
|
|
57
|
+
color: white;
|
|
58
|
+
margin-bottom: 15px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.dashboard {
|
|
62
|
+
background: #1e293b;
|
|
63
|
+
padding: 10px;
|
|
64
|
+
border-radius: 5px;
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.process-card {
|
|
69
|
+
background: #000;
|
|
70
|
+
border: 1px solid #334155;
|
|
71
|
+
padding: 8px;
|
|
72
|
+
margin-top: 8px;
|
|
73
|
+
border-radius: 4px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.pid {
|
|
77
|
+
color: #f59e0b;
|
|
78
|
+
font-weight: bold;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.log-preview {
|
|
82
|
+
font-family: monospace;
|
|
83
|
+
color: #a3e635;
|
|
84
|
+
font-size: 10px;
|
|
85
|
+
white-space: pre-wrap;
|
|
86
|
+
overflow-x: hidden;
|
|
87
|
+
margin-top: 5px;
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
90
|
+
</head>
|
|
91
|
+
|
|
92
|
+
<body>
|
|
93
|
+
<h3>Agent Dashboard</h3>
|
|
94
|
+
<label>Workspace Directory:</label>
|
|
95
|
+
<input type="text" id="dir-input" placeholder="C:\Projects\app">
|
|
96
|
+
<label>Inject Prompt Every N Messages (0 to disable):</label>
|
|
97
|
+
<input type="number" id="inject-n-input" placeholder="5" value="5" min="0">
|
|
98
|
+
<button id="save-btn">Save Directory</button>
|
|
99
|
+
<button id="init-btn">Initialize Agent in Chat</button>
|
|
100
|
+
|
|
101
|
+
<h3>Live Terminals</h3>
|
|
102
|
+
<div class="dashboard" id="process-list">
|
|
103
|
+
<div style="color: #94a3b8;">No active background processes...</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<script src="popup.js"></script>
|
|
107
|
+
</body>
|
|
108
|
+
|
|
109
|
+
</html>
|