mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-05 21:25:27 +02:00
feat: auto-load Chrome extension when $B connect launches Chrome
Extension auto-loads via --load-extension flag — no manual chrome://extensions install needed. findExtensionPath() checks repo root, global install, and dev paths. Also adds bin/gstack-extension helper for manual install in regular Chrome, and rewrites BROWSER.md install docs with auto-load as primary path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+28
-39
@@ -164,54 +164,43 @@ The window has a subtle green shimmer line at the top edge and a floating "gstac
|
||||
|
||||
### Chrome extension (Side Panel)
|
||||
|
||||
A Chrome extension that shows a live activity feed of browse commands in a Side Panel, plus @ref overlays on the page. Works in any Chrome-based browser (Chrome, Comet, Edge).
|
||||
A Chrome extension that shows a live activity feed of browse commands in a Side Panel, plus @ref overlays on the page.
|
||||
|
||||
#### Step-by-step install
|
||||
#### Automatic install (recommended)
|
||||
|
||||
**1. Open the extensions page**
|
||||
When you run `$B connect`, the extension **auto-loads** into the Playwright-controlled Chrome window. No manual steps needed — the Side Panel is immediately available.
|
||||
|
||||
Type `chrome://extensions` in Chrome's address bar and press Enter.
|
||||
|
||||
**2. Enable Developer mode**
|
||||
|
||||
In the top-right corner of the extensions page, toggle the **Developer mode** switch ON. You'll see new buttons appear: "Load unpacked", "Pack extension", and "Update".
|
||||
|
||||
**3. Load the extension**
|
||||
|
||||
Click **Load unpacked**. A file picker dialog opens.
|
||||
|
||||
A file picker opens. You need to navigate to the `extension/` folder inside gstack, but macOS hides folders starting with `.` by default. The easiest way: press **Cmd+Shift+G** in the file picker to open "Go to folder", then paste one of these paths:
|
||||
|
||||
- Global install: `~/.claude/skills/gstack/extension`
|
||||
- Project install: `<your-repo>/.claude/skills/gstack/extension`
|
||||
- Dev/source: `<gstack-repo>/extension`
|
||||
|
||||
Press Enter, then click **Select** (select the `extension/` folder itself, not a file inside it).
|
||||
|
||||
Alternatively, press **Cmd+Shift+.** (period) in the file picker to reveal hidden files, then navigate to `.claude/skills/gstack/extension` manually.
|
||||
|
||||
**4. Pin the extension**
|
||||
|
||||
Click the puzzle piece icon (Extensions) in Chrome's toolbar. Find "gstack browse" and click the pin icon so it's always visible.
|
||||
|
||||
**5. Configure the port**
|
||||
|
||||
Click the gstack icon in the toolbar. A popup appears with a port input field.
|
||||
|
||||
Find your browse server port — run `$B status` or check `.gstack/browse.json` in your project root:
|
||||
```bash
|
||||
cat .gstack/browse.json | grep port
|
||||
# or
|
||||
$B status # shows the port in the output
|
||||
$B connect # launches Chrome with extension pre-loaded
|
||||
# Click the gstack icon in toolbar → Open Side Panel
|
||||
```
|
||||
|
||||
Enter the port number and press Enter. The popup saves it and starts polling.
|
||||
The port is auto-configured. You're done.
|
||||
|
||||
**6. Open the Side Panel**
|
||||
#### Manual install (for your regular Chrome)
|
||||
|
||||
Click the gstack icon again, then click **Open Side Panel**. The Side Panel slides open on the right side of Chrome showing a live activity feed.
|
||||
If you want the extension in your everyday Chrome (not the Playwright-controlled one), run:
|
||||
|
||||
Alternatively, right-click the gstack icon and choose "Open side panel."
|
||||
```bash
|
||||
bin/gstack-extension # opens chrome://extensions, copies path to clipboard
|
||||
```
|
||||
|
||||
Or do it manually:
|
||||
|
||||
1. **Go to `chrome://extensions`** in Chrome's address bar
|
||||
2. **Toggle "Developer mode" ON** (top-right corner)
|
||||
3. **Click "Load unpacked"** — a file picker opens
|
||||
4. **Navigate to the extension folder:** Press **Cmd+Shift+G** in the file picker to open "Go to folder", then paste one of these paths:
|
||||
- Global install: `~/.claude/skills/gstack/extension`
|
||||
- Dev/source: `<gstack-repo>/extension`
|
||||
|
||||
Press Enter, then click **Select**.
|
||||
|
||||
(Tip: macOS hides folders starting with `.` — press **Cmd+Shift+.** in the file picker to reveal them if you prefer to navigate manually.)
|
||||
|
||||
5. **Pin it:** Click the puzzle piece icon (Extensions) in the toolbar → pin "gstack browse"
|
||||
6. **Set the port:** Click the gstack icon → enter the port from `$B status` or `.gstack/browse.json`
|
||||
7. **Open Side Panel:** Click the gstack icon → "Open Side Panel"
|
||||
|
||||
#### What you get
|
||||
|
||||
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# gstack-extension — helper to install the Chrome extension
|
||||
#
|
||||
# When using $B connect, the extension auto-loads. This script is for
|
||||
# installing it in your regular Chrome (not the Playwright-controlled one).
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Find the extension directory
|
||||
EXT_DIR=""
|
||||
if [ -f "$REPO_ROOT/extension/manifest.json" ]; then
|
||||
EXT_DIR="$REPO_ROOT/extension"
|
||||
elif [ -f "$HOME/.claude/skills/gstack/extension/manifest.json" ]; then
|
||||
EXT_DIR="$HOME/.claude/skills/gstack/extension"
|
||||
fi
|
||||
|
||||
if [ -z "$EXT_DIR" ]; then
|
||||
echo "Error: extension/ directory not found."
|
||||
echo "Expected at: $REPO_ROOT/extension/ or ~/.claude/skills/gstack/extension/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy path to clipboard
|
||||
echo -n "$EXT_DIR" | pbcopy 2>/dev/null
|
||||
|
||||
# Get browse server port
|
||||
PORT=""
|
||||
STATE_FILE="$REPO_ROOT/.gstack/browse.json"
|
||||
if [ -f "$STATE_FILE" ]; then
|
||||
PORT=$(grep -o '"port":[0-9]*' "$STATE_FILE" | grep -o '[0-9]*')
|
||||
fi
|
||||
|
||||
echo "gstack Chrome Extension Setup"
|
||||
echo "=============================="
|
||||
echo ""
|
||||
echo "Extension path (copied to clipboard):"
|
||||
echo " $EXT_DIR"
|
||||
echo ""
|
||||
|
||||
if [ -n "$PORT" ]; then
|
||||
echo "Browse server port: $PORT"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "Quick install (if using \$B connect):"
|
||||
echo " The extension auto-loads when you run \$B connect."
|
||||
echo " No manual installation needed!"
|
||||
echo ""
|
||||
echo "Manual install (for your regular Chrome):"
|
||||
echo ""
|
||||
echo " 1. Opening chrome://extensions now..."
|
||||
|
||||
# Open chrome://extensions
|
||||
osascript -e 'tell application "Google Chrome" to open location "chrome://extensions"' 2>/dev/null || \
|
||||
open "chrome://extensions" 2>/dev/null || \
|
||||
echo " Could not open Chrome. Navigate to chrome://extensions manually."
|
||||
|
||||
echo " 2. Toggle 'Developer mode' ON (top-right)"
|
||||
echo " 3. Click 'Load unpacked'"
|
||||
echo " 4. In the file picker: Cmd+Shift+G → paste (path is in your clipboard) → Enter → Select"
|
||||
echo " 5. Click the gstack puzzle icon in toolbar → enter port: ${PORT:-<check \$B status>}"
|
||||
echo " 6. Click 'Open Side Panel'"
|
||||
@@ -70,6 +70,39 @@ export class BrowserManager {
|
||||
|
||||
getConnectionMode(): 'launched' | 'cdp' { return this.connectionMode; }
|
||||
|
||||
/**
|
||||
* Find the gstack Chrome extension directory.
|
||||
* Checks: repo root /extension, global install, dev install.
|
||||
*/
|
||||
private findExtensionPath(): string | null {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const candidates = [
|
||||
// Relative to this source file (dev mode: browse/src/ -> ../../extension)
|
||||
path.resolve(__dirname, '..', '..', 'extension'),
|
||||
// Global gstack install
|
||||
path.join(process.env.HOME || '', '.claude', 'skills', 'gstack', 'extension'),
|
||||
// Git repo root (detected via BROWSE_STATE_FILE location)
|
||||
(() => {
|
||||
const stateFile = process.env.BROWSE_STATE_FILE || '';
|
||||
if (stateFile) {
|
||||
const repoRoot = path.resolve(path.dirname(stateFile), '..');
|
||||
return path.join(repoRoot, '.claude', 'skills', 'gstack', 'extension');
|
||||
}
|
||||
return '';
|
||||
})(),
|
||||
].filter(Boolean);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
if (fs.existsSync(path.join(candidate, 'manifest.json'))) {
|
||||
return candidate;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ref map for external consumers (e.g., /refs endpoint).
|
||||
*/
|
||||
@@ -125,12 +158,20 @@ export class BrowserManager {
|
||||
this.refMap.clear();
|
||||
this.nextTabId = 1;
|
||||
|
||||
// Find the gstack extension directory for auto-loading
|
||||
const extensionPath = this.findExtensionPath();
|
||||
const launchArgs = ['--restore-last-session'];
|
||||
if (extensionPath) {
|
||||
launchArgs.push(`--disable-extensions-except=${extensionPath}`);
|
||||
launchArgs.push(`--load-extension=${extensionPath}`);
|
||||
}
|
||||
|
||||
// Launch real Chrome via Playwright's channel protocol
|
||||
// This uses the system Chrome binary, headed, with real window
|
||||
this.browser = await chromium.launch({
|
||||
channel: 'chrome',
|
||||
headless: false,
|
||||
args: ['--restore-last-session'],
|
||||
args: launchArgs,
|
||||
});
|
||||
this.connectionMode = 'cdp';
|
||||
this.intentionalDisconnect = false;
|
||||
|
||||
Reference in New Issue
Block a user