@pa1nd/horse-browser 0.4.0

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.
package/install.sh ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env bash
2
+ # One-time setup for horse-browser. Safe to re-run.
3
+ #
4
+ # 1. Fetches Chrome for Testing (a dedicated, automation-purposed browser that
5
+ # coexists with your daily browser) via @puppeteer/browsers — you install
6
+ # nothing by hand. Override with HORSE_BROWSER_BIN=/path/to/chromium to use
7
+ # your own Chromium instead.
8
+ # 2. Writes config + symlinks the `horse-browser` launcher onto your PATH.
9
+ # 3. Launches the browser for the first time (sign into your apps — logins
10
+ # persist), then smoke-tests the whole chain via browser-harness if present.
11
+ #
12
+ # Env overrides: HORSE_BROWSER_BIN, HORSE_BROWSER_PORT (9223),
13
+ # HORSE_BROWSER_PROFILE (~/.config/horse-browser/profile).
14
+ set -euo pipefail
15
+
16
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ PORT="${HORSE_BROWSER_PORT:-9223}"
18
+ PROFILE="${HORSE_BROWSER_PROFILE:-$HOME/.config/horse-browser/profile}"
19
+ CONFIG_DIR="$HOME/.config/horse-browser"
20
+ CONFIG="$CONFIG_DIR/config"
21
+ CACHE="$HOME/.cache/horse-browser"
22
+ BINDIR="$HOME/.local/bin"
23
+ EXT="$HERE/extension"
24
+
25
+ mkdir -p "$CONFIG_DIR" "$BINDIR" "$CACHE"
26
+
27
+ # 1. browser ──────────────────────────────────────────────────────────────────
28
+ BIN="${HORSE_BROWSER_BIN:-}"
29
+ if [ -n "$BIN" ]; then
30
+ echo "Using your nominated browser: $BIN"
31
+ else
32
+ if ! command -v npx >/dev/null 2>&1; then
33
+ echo "ERROR: npx (Node) not found — needed to fetch Chrome for Testing." >&2
34
+ echo " Install Node, or set HORSE_BROWSER_BIN to a Chromium binary, then re-run." >&2
35
+ exit 1
36
+ fi
37
+ echo "Fetching Chrome for Testing via @puppeteer/browsers (one-time, ~170MB)…"
38
+ out="$(npx -y @puppeteer/browsers install chrome@stable --path "$CACHE")"
39
+ # output line: "chrome@<version> <path-to-executable>" (path may contain spaces)
40
+ BIN="$(printf '%s\n' "$out" | grep '^chrome@' | tail -1 | sed 's/^[^ ]* //')"
41
+ fi
42
+ if [ ! -x "$BIN" ]; then
43
+ echo "ERROR: browser binary not found / not executable: $BIN" >&2
44
+ exit 1
45
+ fi
46
+ echo "✓ browser: $BIN"
47
+
48
+ # 2. config + launcher on PATH ─────────────────────────────────────────────────
49
+ cat > "$CONFIG" <<EOF
50
+ # horse-browser config — written by install.sh
51
+ BROWSER_BIN="$BIN"
52
+ EXTENSION_DIR="$EXT"
53
+ PORT="$PORT"
54
+ PROFILE="$PROFILE"
55
+ EOF
56
+ # When installed via npm (`npm i -g @pa1nd/horse-browser`), npm owns the launcher
57
+ # symlink (package.json "bin") — don't lay down a second, competing one in ~/.local/bin.
58
+ if [ -n "${HORSE_FROM_NPM:-}" ]; then
59
+ echo "✓ launcher: managed by npm (bin: horse-browser) (config: $CONFIG)"
60
+ else
61
+ ln -sf "$HERE/bin/horse-browser" "$BINDIR/horse-browser"
62
+ echo "✓ launcher: $BINDIR/horse-browser (config: $CONFIG)"
63
+ case ":$PATH:" in *":$BINDIR:"*) ;; *) echo " note: $BINDIR isn't on your PATH — add it so 'horse-browser' resolves";; esac
64
+ fi
65
+
66
+ # 3. bh_open helpers into browser-harness ───────────────────────────────────────
67
+ # browser-harness auto-loads <workspace>/agent_helpers.py on every call; we append our
68
+ # bh_open/bh_list/bh_switch_tab helpers there so they exist on the first run — no agent
69
+ # installs the recipe by hand. The workspace location varies by version/install (≤0.1.0:
70
+ # <repo>/agent-workspace; 0.1.1+: ~/.config/browser-harness/agent-workspace; or whatever
71
+ # BH_AGENT_WORKSPACE points at) — so instead of guessing, we ASK browser-harness itself
72
+ # where it loads from via its own python (helpers.AGENT_WORKSPACE). Append-once.
73
+ if ! command -v browser-harness >/dev/null 2>&1; then
74
+ # browser-harness is a separate (Python) prerequisite. In a hand/curl install it's
75
+ # required up front. Under npm it's a *declared* prereq the user may not have yet — so
76
+ # warn and finish cleanly (config + Chrome are already in place; re-run setup once
77
+ # browser-harness is installed to get the bh_open helpers) rather than failing the install.
78
+ echo "! browser-harness not found on PATH — skipping the bh_open helper sync." >&2
79
+ echo " Install it (e.g. 'uv tool install browser-harness'), then re-run setup." >&2
80
+ [ -n "${HORSE_FROM_NPM:-}" ] && exit 0
81
+ echo " (https://github.com/browser-use/browser-harness)" >&2
82
+ exit 1
83
+ fi
84
+ HELPERS_SRC="$HERE/agent-helpers.py"
85
+ HELPERS_MARKER="# ── horse-browser helpers (installed by horse-browser/install.sh) ──"
86
+ install_helpers_into() { # $1 = workspace dir; (re)syncs our helpers block — idempotent, so
87
+ local dst="$1/agent_helpers.py" # re-running install.sh deploys helper UPDATES, not just the first time.
88
+ mkdir -p "$1" 2>/dev/null || return 0
89
+ # drop any previously-installed block (marker → EOF), trim trailing blanks, then re-append
90
+ if [ -f "$dst" ] && grep -qF "$HELPERS_MARKER" "$dst"; then
91
+ awk -v m="$HELPERS_MARKER" 'index($0,m){exit} {print}' "$dst" \
92
+ | awk 'NF{last=NR} {b[NR]=$0} END{for(i=1;i<=last;i++)print b[i]}' > "$dst.tmp" && mv "$dst.tmp" "$dst"
93
+ fi
94
+ { [ -s "$dst" ] && printf '\n\n'; printf '%s\n' "$HELPERS_MARKER"; cat "$HELPERS_SRC"; } >> "$dst"
95
+ echo "✓ synced bh_open helpers → $dst"
96
+ }
97
+ # (a) the workspace browser-harness ACTUALLY loads from — ask it via its own python
98
+ # (the CLI's shebang points at it); helpers.AGENT_WORKSPACE honours BH_AGENT_WORKSPACE and
99
+ # resolves the right default on every version. Fall back to known defaults if the query fails.
100
+ WS_NEW=""
101
+ BHPY="$(head -1 "$(command -v browser-harness)" 2>/dev/null | sed 's/^#!//;s/ .*//')"
102
+ [ -n "$BHPY" ] && [ -x "$BHPY" ] && WS_NEW="$("$BHPY" -c 'from browser_harness.helpers import AGENT_WORKSPACE; print(AGENT_WORKSPACE)' 2>/dev/null)"
103
+ [ -z "$WS_NEW" ] && WS_NEW="${BH_AGENT_WORKSPACE:-$HOME/.config/browser-harness/agent-workspace}"
104
+ install_helpers_into "$WS_NEW"
105
+ # (b) a source checkout's agent-workspace, if present (editable/dev installs)
106
+ BH_DIR="${BROWSER_HARNESS_DIR:-}"
107
+ if [ -z "$BH_DIR" ]; then
108
+ for c in "$HOME/Developer/browser-harness" "$HOME/browser-harness"; do
109
+ [ -f "$c/agent-workspace/agent_helpers.py" ] && { BH_DIR="$c"; break; }
110
+ done
111
+ fi
112
+ [ -n "$BH_DIR" ] && [ "$BH_DIR/agent-workspace" != "$WS_NEW" ] && install_helpers_into "$BH_DIR/agent-workspace"
113
+
114
+ # Keep the stable symlink that ~/.claude/CLAUDE.md's @-import points at aimed at the current
115
+ # (Python-version-specific) packaged SKILL, so the import never rots across reinstalls. We
116
+ # only refresh the symlink — registering the block in CLAUDE.md is opt-in (./claude-md.sh apply).
117
+ # Under npm we don't reach into ~/.claude from a silent postinstall — the next-steps
118
+ # note tells the user how to wire SKILL.md into their CLAUDE.md themselves.
119
+ if [ -z "${HORSE_FROM_NPM:-}" ]; then
120
+ "$HERE/claude-md.sh" symlink || echo " (skipped CLAUDE.md SKILL symlink — see ./claude-md.sh)" >&2
121
+ fi
122
+
123
+ # 4. first launch + smoke test ─────────────────────────────────────────────────
124
+ # HORSE_SKIP_LAUNCH=1 skips this whole step — used by the "update" path (re-running
125
+ # install for a fresh pull) where relaunching the browser + a 40s smoke test would
126
+ # be noise. Steps 1–3 (browser fetch, config, launcher, helpers) still run.
127
+ if [ -n "${HORSE_SKIP_LAUNCH:-}" ]; then
128
+ echo "✓ setup refreshed (skipped launch/smoke-test: HORSE_SKIP_LAUNCH set)"
129
+ echo " Restart to pick up changes: horse-browser"
130
+ exit 0
131
+ fi
132
+
133
+ echo "Launching for the first time…"
134
+ "$BINDIR/horse-browser" || true
135
+
136
+ if command -v browser-harness >/dev/null 2>&1; then
137
+ export BU_CDP_URL="http://127.0.0.1:$PORT"
138
+ read -r -d '' check <<'PY' || true
139
+ import browser_harness.helpers as _h
140
+ from browser_harness.helpers import cdp
141
+ # our helpers must have loaded AND overridden cdp (so auto-home is active), not just added bh_open
142
+ if not hasattr(_h, "bh_open") or getattr(_h.cdp, "__module__", "") != "browser_harness_agent_helpers":
143
+ print("NOHELPERS"); raise SystemExit(0)
144
+ sw = next((t["targetId"] for t in cdp("Target.getTargets")["targetInfos"]
145
+ if t.get("type") == "service_worker"
146
+ and t.get("url", "").startswith("chrome-extension://")), None)
147
+ if not sw:
148
+ print("PENDING"); raise SystemExit(0)
149
+ s = cdp("Target.attachToTarget", targetId=sw, flatten=True)["sessionId"]
150
+ r = cdp("Runtime.evaluate", session_id=s,
151
+ expression="self.listTabs ? self.listTabs('__install_check__') : 'NOFN'",
152
+ awaitPromise=True, returnByValue=True)
153
+ cdp("Target.detachFromTarget", sessionId=s)
154
+ print("READY" if isinstance(r.get("result", {}).get("value"), list) else "PENDING")
155
+ PY
156
+ echo "Verifying through browser-harness…"
157
+ verified=""; result=""
158
+ for _ in $(seq 1 20); do # ~40s — must clear the SW's first 30s keepalive tick
159
+ result="$(printf '%s' "$check" | browser-harness 2>/dev/null | tail -1)"
160
+ [ "$result" = "READY" ] && { verified=1; break; }
161
+ [ "$result" = "NOHELPERS" ] && break # helpers not loading — no point retrying
162
+ sleep 2
163
+ done
164
+ if [ -n "$verified" ]; then
165
+ echo "✓ verified — bh_open loaded + listTabs() answered over CDP; the extension is live"
166
+ elif [ "$result" = "NOHELPERS" ]; then
167
+ echo "! bh_open did NOT load — browser-harness isn't reading the workspace we appended to." >&2
168
+ echo " Set BH_AGENT_WORKSPACE to its agent-workspace dir and re-run ./install.sh." >&2
169
+ else
170
+ echo "! couldn't confirm the extension within ~40s — check the browser window."
171
+ fi
172
+ fi
173
+
174
+ echo
175
+ echo "Next:"
176
+ echo " • Sign into the apps you want your agents to use — logins persist in $PROFILE"
177
+ echo " • Point CDP clients at it: export BU_CDP_URL=http://127.0.0.1:$PORT"
178
+ echo " • (Re)launch anytime — agents too — with: horse-browser"
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@pa1nd/horse-browser",
3
+ "version": "0.4.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "A dedicated, focus-safe browser for AI agents: per-session colored tab groups, a live monitor wall, and a browser-harness drop-in launcher. Log in once — every agent inherits the session, and none of them steals your focus.",
8
+ "bin": {
9
+ "horse-browser": "bin/horse-browser"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "extension",
14
+ "agent-helpers.py",
15
+ "scripts",
16
+ "install.sh",
17
+ "claude-md.sh",
18
+ "SKILL.md"
19
+ ],
20
+ "scripts": {
21
+ "postinstall": "bash scripts/postinstall.sh"
22
+ },
23
+ "dependencies": {
24
+ "@puppeteer/browsers": "^3.0.6"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "os": [
30
+ "darwin"
31
+ ],
32
+ "license": "MIT",
33
+ "author": "pA1nD",
34
+ "homepage": "https://github.com/pA1nD/horse-browser#readme",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/pA1nD/horse-browser.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/pA1nD/horse-browser/issues"
41
+ },
42
+ "keywords": [
43
+ "browser",
44
+ "cdp",
45
+ "chrome-devtools",
46
+ "ai-agents",
47
+ "browser-automation",
48
+ "browser-harness",
49
+ "puppeteer",
50
+ "tab-groups",
51
+ "macos"
52
+ ]
53
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # npm postinstall for @pa1nd/horse-browser.
3
+ #
4
+ # Runs the package's own install.sh in "npm mode" (HORSE_FROM_NPM) so all the
5
+ # battle-tested setup — fetch Chrome for Testing, write ~/.config/horse-browser/config
6
+ # pointing at THIS install's extension/, sync the bh_open helpers if browser-harness is
7
+ # present — lives in exactly one place. npm mode differs only in what npm already owns
8
+ # or what a silent install shouldn't touch: it skips the ~/.local/bin launcher symlink
9
+ # (npm's "bin" field handles it), skips the ~/.claude SKILL symlink, and doesn't launch
10
+ # the browser / smoke-test on install (HORSE_SKIP_LAUNCH).
11
+ #
12
+ # This must NEVER fail `npm install`: a missing browser-harness, an offline Chrome fetch,
13
+ # or any hiccup degrades to a printed next-step, not a broken install. `horse-browser
14
+ # update` re-fetches Chrome later; re-running setup syncs helpers once browser-harness is in.
15
+ set -u
16
+
17
+ PKG="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
18
+ cd "$PKG" || exit 0 # cwd = package root so `npx @puppeteer/browsers` resolves the bundled fetcher
19
+
20
+ echo "horse-browser · setting up (Chrome for Testing + config)…"
21
+ HORSE_FROM_NPM=1 HORSE_SKIP_LAUNCH=1 bash "$PKG/install.sh" || \
22
+ echo "horse-browser · setup didn't fully complete — run 'horse-browser update' to fetch Chrome." >&2
23
+
24
+ cat <<EOF
25
+
26
+ horse-browser installed. Next:
27
+ • Start it (launches the browser, no focus steal): horse-browser
28
+ • Sign into your apps once — logins persist for every agent.
29
+ • Prereq for driving it: browser-harness (a Python tool) —
30
+ uv tool install browser-harness # or: pipx install browser-harness
31
+ • Teach agents the bh_open discipline — add this to your ~/.claude/CLAUDE.md:
32
+ @$PKG/SKILL.md
33
+ EOF
34
+ exit 0