Skip to content

Instantly share code, notes, and snippets.

@rkmax
Last active July 29, 2025 13:49
Show Gist options
  • Save rkmax/cbaddddb5656692e037d9973ce72ccb5 to your computer and use it in GitHub Desktop.
Save rkmax/cbaddddb5656692e037d9973ce72ccb5 to your computer and use it in GitHub Desktop.
Routes external links to your currently active Firefox profile instead of the default one. Useful when running multiple Firefox profiles simultaneously. Includes installer and desktop integration for Hyprland/Wayland systems.

Firefox Broker

Routes external links to the most recently active Firefox profile, solving Firefox's limitation of always opening links in the default profile.

Overview

Firefox Broker is a TypeScript/Deno utility that intelligently routes URLs to the appropriate Firefox profile based on window focus history. Instead of always opening links in the default profile, it identifies which Firefox instance was most recently active and opens the link there.

Features

  • Routes URLs to most recently focused Firefox window
  • Automatic profile detection from process command line arguments
  • Fallback to new instance if existing Firefox can't handle the request
  • Window focus management via Hyprland compositor
  • Caching for improved performance
  • Debug logging and error handling

Requirements

  • Deno - TypeScript runtime
  • hyprctl - Hyprland window manager control
  • jq - JSON processor
  • firefox - Mozilla Firefox browser

Installation

  1. Make the script executable:

    chmod +x firefox-broker.ts
  2. Set up as default browser handler (optional):

    ./install-firefox-broker.sh

Usage

./firefox-broker.ts <URL>

Environment Variables

  • FIREFOX_BROKER_DEBUG=1 - Enable debug logging (default: enabled)
  • FIREFOX_BROKER_SILENT=1 - Suppress error messages
  • FIREFOX_BROKER_DEBUG=0 - Disable debug output

Examples

# Open URL in most recent Firefox profile
./firefox-broker.ts https://example.com

# Show help
./firefox-broker.ts --help

How It Works

  1. Window Detection: Uses hyprctl to get all Firefox windows with focus history
  2. Profile Detection: Reads process command line arguments to identify profiles
  3. Target Selection: Chooses the window with the lowest focus history ID (most recent)
  4. URL Routing: Opens the URL in the target profile using --new-tab
  5. Window Focus: Activates the Firefox window after opening the URL

Profile Detection

The broker detects Firefox profiles by examining process command lines for:

  • -P <profile-name> arguments
  • --profile <path> arguments
  • Falls back to "default" profile if none found

Caching

Window information is cached for 2 seconds to improve performance during rapid URL opening.

Error Handling

  • Validates URLs for security (checks schemes and dangerous characters)
  • Graceful fallback to new Firefox instance if existing instance fails
  • Dependency checking on startup
  • Process cleanup on exit
#!/bin/bash
# firefox-broker - Routes external links to most recently active Firefox profile
# Solves Firefox's limitation of always opening links in default profile
# See: https://support.mozilla.org/en-US/questions/999493
set -euo pipefail
readonly DEBUG="${FIREFOX_BROKER_DEBUG:-1}" # Default DEBUG=1 for xdg-open compatibility
readonly SILENT="${FIREFOX_BROKER_SILENT:-0}"
readonly CACHE_FILE="/tmp/.firefox-broker-cache-$$"
trap 'rm -f "$CACHE_FILE"' EXIT
log_debug() { [[ "$DEBUG" == "1" ]] && echo "[DEBUG] $*" >&2; }
log_error() { [[ "$SILENT" != "1" ]] && echo "[ERROR] $*" >&2; }
log_info() { [[ "$DEBUG" == "1" ]] && echo "[INFO] $*" >&2; }
check_dependencies() {
local missing=()
for dep in hyprctl jq firefox; do
command -v "$dep" >/dev/null || missing+=("$dep")
done
[[ ${#missing[@]} -gt 0 ]] && { log_error "Missing: ${missing[*]}"; exit 1; }
}
get_firefox_windows() {
local force_refresh="${1:-false}"
if [[ "$force_refresh" != "true" && -f "$CACHE_FILE" && $(($(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 999))) -lt 2 ]]; then
log_debug "Using cached window info"
cat "$CACHE_FILE"
return
fi
log_debug "Fetching window info from hyprctl"
timeout 10 hyprctl clients -j 2>/dev/null | \
jq -r '.[] | select(.class == "firefox") | "\(.pid):\(.focusHistoryID):\(.address):\(.title)"' > "$CACHE_FILE" || {
log_error "Failed to get window info"; return 1; }
cat "$CACHE_FILE"
}
detect_profile_for_pid() {
local pid="$1" profile="default" cmdline
log_debug "Detecting profile for PID: $pid"
[[ ! -d "/proc/$pid" ]] && { echo "$profile"; return; }
cmdline=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null) || { echo "$profile"; return; }
log_debug "Command line for PID $pid: $cmdline"
if [[ $cmdline =~ -P[[:space:]]+([^[:space:]]+) ]]; then
profile="${BASH_REMATCH[1]}"
log_debug "Found profile: $profile"
elif [[ $cmdline =~ --profile[[:space:]]+([^[:space:]]+) ]] && [[ ${BASH_REMATCH[1]} =~ /([^/]+)$ ]]; then
profile="${BASH_REMATCH[1]%.*}"
log_debug "Found profile path: $profile"
fi
echo "$profile"
}
find_most_recent_firefox() {
local windows most_recent_pid="" most_recent_focus=999999 most_recent_profile="default"
log_debug "Finding most recent Firefox window"
windows=$(get_firefox_windows true) || { echo "$most_recent_profile"; return; }
[[ -z "$windows" ]] && { log_debug "No Firefox windows open"; echo "$most_recent_profile"; return; }
while IFS=':' read -r pid focus_id address title; do
log_debug "Window: PID=$pid, Focus=$focus_id, Title=$title"
if [[ $focus_id -lt $most_recent_focus ]]; then
most_recent_focus=$focus_id
most_recent_pid=$pid
log_debug "New most recent: PID=$pid, Focus=$focus_id"
fi
done <<< "$windows"
[[ -n "$most_recent_pid" ]] && most_recent_profile=$(detect_profile_for_pid "$most_recent_pid")
log_info "Most recent Firefox: PID=$most_recent_pid, Profile=$most_recent_profile"
echo "$most_recent_profile"
}
validate_url() {
local url="$1"
[[ ! $url =~ ^(https?|file|ftp)://.*$ && ! $url =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}.*$ ]] && { log_error "Invalid URL: $url"; return 1; }
[[ $url =~ [\;\|\&\$\`] ]] && { log_error "Dangerous chars in URL: $url"; return 1; }
}
focus_firefox_window() {
local windows most_recent_address="" most_recent_focus=999999
windows=$(get_firefox_windows true) || return 1
while IFS=':' read -r pid focus_id address title; do
[[ $focus_id -lt $most_recent_focus ]] && { most_recent_focus=$focus_id; most_recent_address=$address; }
done <<< "$windows"
[[ -n "$most_recent_address" ]] && timeout 10 hyprctl dispatch focuswindow "address:$most_recent_address" >/dev/null 2>&1
}
# Route URL to the appropriate Firefox profile
route_url() {
local url="$1"
local target_profile
local firefox_windows
log_info "Routing URL: $url"
# Validate URL
if ! validate_url "$url"; then
exit 1
fi
# Find target profile
target_profile=$(find_most_recent_firefox)
log_info "Target profile: $target_profile"
# Check if Firefox is running - force fresh data
if firefox_windows=$(get_firefox_windows true) && [[ -n "$firefox_windows" ]]; then
log_debug "Firefox is running, attempting to open in existing instance"
# Try to open URL in existing Firefox instance
local firefox_cmd=("firefox")
# Add profile specification if not default
if [[ "$target_profile" != "default" ]]; then
firefox_cmd+=("-P" "$target_profile")
fi
# Add URL opening arguments for existing instance
firefox_cmd+=("--new-tab" "$url")
log_debug "Executing: ${firefox_cmd[*]}"
# Execute Firefox command (will send to existing instance)
if "${firefox_cmd[@]}" >/dev/null 2>&1; then
log_info "URL opened in existing Firefox instance (profile: $target_profile)"
# Give Firefox time to process the command
sleep 0.3
focus_firefox_window
exit 0
else
log_debug "Failed to open in existing instance, trying alternative method"
fi
# Alternative: try firefox --remote command
if firefox --remote "openURL($url,new-tab)" >/dev/null 2>&1; then
log_info "URL opened using Firefox remote command"
sleep 0.3
focus_firefox_window
exit 0
else
log_debug "Firefox remote command failed"
fi
else
log_debug "No Firefox windows found, starting new instance"
fi
# Fallback: start new Firefox instance
local firefox_cmd=("firefox")
# Add profile specification if not default
if [[ "$target_profile" != "default" ]]; then
firefox_cmd+=("-P" "$target_profile")
fi
# Add URL as startup argument
firefox_cmd+=("$url")
log_debug "Executing fallback: ${firefox_cmd[*]}"
# Execute Firefox command in background
"${firefox_cmd[@]}" >/dev/null 2>&1 &
local firefox_pid=$!
# Check if Firefox process started successfully
sleep 0.2
if kill -0 "$firefox_pid" 2>/dev/null; then
log_info "Firefox started with new instance, PID: $firefox_pid"
# Give Firefox time to start, then focus
sleep 1.0
focus_firefox_window
exit 0
else
log_error "Failed to start Firefox"
exit 1
fi
}
# Display usage information
show_usage() {
cat << EOF
Usage: firefox-broker <URL>
Routes external links to most recently active Firefox profile.
Environment: FIREFOX_BROKER_DEBUG=1 (default), FIREFOX_BROKER_SILENT=1
Requires: hyprctl, jq, firefox
EOF
}
# Main execution
main() {
# Handle help requests
if [[ $# -eq 0 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then
show_usage
exit 0
fi
# Check dependencies
check_dependencies
# Route the URL
route_url "$1"
}
# Execute main function if script is run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
[Desktop Entry]
Version=1.1
Type=Application
Name=Firefox Broker
GenericName=Intelligent Web Browser Router
Comment=Routes links to most recently active Firefox profile
Icon=firefox
Exec=firefox-broker %u
Actions=new-window;new-private-window;profile-manager;
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;
StartupNotify=false
Categories=Network;WebBrowser;
Keywords=Internet;WWW;Browser;Web;Explorer;Firefox;Profile;Broker;
StartupWMClass=firefox
NoDisplay=false
[Desktop Action new-window]
Name=Open a New Window
Exec=firefox-broker --new-window
[Desktop Action new-private-window]
Name=Open a New Private Window
Exec=firefox-broker --private-window
[Desktop Action profile-manager]
Name=Open Profile Manager
Exec=firefox --ProfileManager
#!/usr/bin/env -S deno run --allow-all
/**
* firefox-broker.ts - Routes external links to most recently active Firefox profile
* Solves Firefox's limitation of always opening links in default profile
* See: https://support.mozilla.org/en-US/questions/999493
*/
import { existsSync } from "https://deno.land/[email protected]/fs/exists.ts";
// Client interface for hyprctl JSON output
interface HyprClient {
class: string;
pid: number;
focusHistoryID: number;
address: string;
title: string;
}
// Configuration from environment
const DEBUG = Deno.env.get("FIREFOX_BROKER_DEBUG") !== "0"; // Default true for xdg-open compatibility
const SILENT = Deno.env.get("FIREFOX_BROKER_SILENT") === "1";
const CACHE_FILE = `/tmp/.firefox-broker-cache-${Deno.pid}`;
// Window info interface
interface FirefoxWindow {
pid: number;
focusHistoryID: number;
address: string;
title: string;
}
// Logging functions
const logDebug = (...args: unknown[]): void => {
if (DEBUG) console.error("[DEBUG]", ...args);
};
const logError = (...args: unknown[]): void => {
if (!SILENT) console.error("[ERROR]", ...args);
};
const logInfo = (...args: unknown[]): void => {
if (DEBUG) console.error("[INFO]", ...args);
};
// Cleanup on exit
globalThis.addEventListener("unload", () => {
try {
if (existsSync(CACHE_FILE)) {
Deno.removeSync(CACHE_FILE);
}
} catch {
// Ignore cleanup errors
}
});
async function checkDependencies(): Promise<void> {
const missing: string[] = [];
const deps = ["hyprctl", "jq", "firefox"];
for (const dep of deps) {
try {
const cmd = new Deno.Command("which", { args: [dep] });
const result = await cmd.output();
if (!result.success) {
missing.push(dep);
}
} catch {
missing.push(dep);
}
}
if (missing.length > 0) {
logError(`Missing dependencies: ${missing.join(", ")}`);
Deno.exit(1);
}
}
async function getFirefoxWindows(forceRefresh = false): Promise<FirefoxWindow[]> {
try {
// Check cache if not forcing refresh
if (!forceRefresh && existsSync(CACHE_FILE)) {
const stat = await Deno.stat(CACHE_FILE);
const age = (Date.now() - (stat.mtime?.getTime() || 0)) / 1000;
if (age < 2) {
logDebug("Using cached window info");
const cached = await Deno.readTextFile(CACHE_FILE);
return JSON.parse(cached);
}
}
logDebug("Fetching window info from hyprctl");
// Run hyprctl command with timeout
const cmd = new Deno.Command("timeout", {
args: ["10", "hyprctl", "clients", "-j"],
stdout: "piped",
stderr: "piped"
});
const result = await cmd.output();
if (!result.success) {
logError("Failed to get window info from hyprctl");
return [];
}
const jsonText = new TextDecoder().decode(result.stdout);
const clients: HyprClient[] = JSON.parse(jsonText);
const firefoxWindows: FirefoxWindow[] = clients
.filter((client) => client.class === "firefox")
.map((client) => ({
pid: client.pid,
focusHistoryID: client.focusHistoryID,
address: client.address,
title: client.title
}));
// Cache the results
await Deno.writeTextFile(CACHE_FILE, JSON.stringify(firefoxWindows));
return firefoxWindows;
} catch (error) {
logError("Error getting Firefox windows:", error);
return [];
}
}
async function detectProfileForPid(pid: number): Promise<string> {
logDebug(`Detecting profile for PID: ${pid}`);
try {
const cmdlinePath = `/proc/${pid}/cmdline`;
if (!existsSync(cmdlinePath)) {
return "default";
}
const cmdlineBytes = await Deno.readFile(cmdlinePath);
const cmdline = new TextDecoder().decode(cmdlineBytes).replace(/\0/g, " ");
logDebug(`Command line for PID ${pid}: ${cmdline}`);
// Check for -P profile argument
const profileMatch = cmdline.match(/-P\s+([^\s]+)/);
if (profileMatch) {
const profile = profileMatch[1];
logDebug(`Found profile: ${profile}`);
return profile;
}
// Check for --profile path argument
const profilePathMatch = cmdline.match(/--profile\s+([^\s]+)/);
if (profilePathMatch) {
const profilePath = profilePathMatch[1];
const pathParts = profilePath.split("/");
const profile = pathParts[pathParts.length - 1].replace(/\.[^.]+$/, "");
logDebug(`Found profile path: ${profile}`);
return profile;
}
return "default";
} catch (error) {
logDebug(`Error detecting profile for PID ${pid}:`, error);
return "default";
}
}
async function findMostRecentFirefox(): Promise<string> {
logDebug("Finding most recent Firefox window");
const windows = await getFirefoxWindows(true);
if (windows.length === 0) {
logDebug("No Firefox windows open");
return "default";
}
let mostRecentWindow: FirefoxWindow | null = null;
let lowestFocusId = Infinity;
for (const window of windows) {
logDebug(`Window: PID=${window.pid}, Focus=${window.focusHistoryID}, Title=${window.title}`);
if (window.focusHistoryID < lowestFocusId) {
lowestFocusId = window.focusHistoryID;
mostRecentWindow = window;
logDebug(`New most recent: PID=${window.pid}, Focus=${window.focusHistoryID}`);
}
}
if (mostRecentWindow) {
const profile = await detectProfileForPid(mostRecentWindow.pid);
logInfo(`Most recent Firefox: PID=${mostRecentWindow.pid}, Profile=${profile}`);
return profile;
}
return "default";
}
function validateUrl(url: string): boolean {
// Check for valid URL scheme or domain-like pattern
if (!url.match(/^(https?|file|ftp):\/\/.*$/) &&
!url.match(/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}.*$/)) {
logError(`Invalid URL: ${url}`);
return false;
}
// Check for dangerous characters
if (url.match(/[;|&$`]/)) {
logError(`Dangerous characters in URL: ${url}`);
return false;
}
return true;
}
async function focusFirefoxWindow(): Promise<void> {
try {
const windows = await getFirefoxWindows(true);
if (windows.length === 0) return;
let mostRecentWindow: FirefoxWindow | null = null;
let lowestFocusId = Infinity;
for (const window of windows) {
if (window.focusHistoryID < lowestFocusId) {
lowestFocusId = window.focusHistoryID;
mostRecentWindow = window;
}
}
if (mostRecentWindow) {
const cmd = new Deno.Command("timeout", {
args: ["10", "hyprctl", "dispatch", "focuswindow", `address:${mostRecentWindow.address}`],
stdout: "null",
stderr: "null"
});
await cmd.output();
}
} catch (error) {
logDebug("Error focusing Firefox window:", error);
}
}
async function runFirefoxCommand(args: string[]): Promise<boolean> {
try {
logDebug(`Executing: firefox ${args.join(" ")}`);
const cmd = new Deno.Command("firefox", {
args,
stdout: "null",
stderr: "null"
});
const result = await cmd.output();
return result.success;
} catch (error) {
logDebug("Firefox command error:", error);
return false;
}
}
async function routeUrl(url: string): Promise<void> {
logInfo(`Routing URL: ${url}`);
// Validate URL
if (!validateUrl(url)) {
Deno.exit(1);
}
// Find target profile
const targetProfile = await findMostRecentFirefox();
logInfo(`Target profile: ${targetProfile}`);
// Check if Firefox is running
const firefoxWindows = await getFirefoxWindows(true);
if (firefoxWindows.length > 0) {
logDebug("Firefox is running, attempting to open in existing instance");
// Build Firefox command
const firefoxCmd: string[] = [];
// Add profile specification if not default
if (targetProfile !== "default") {
firefoxCmd.push("-P", targetProfile);
}
// Add URL opening arguments
firefoxCmd.push("--new-tab", url);
// Try to open URL in existing Firefox instance
if (await runFirefoxCommand(firefoxCmd)) {
logInfo(`URL opened in existing Firefox instance (profile: ${targetProfile})`);
// Give Firefox time to process the command
await new Promise(resolve => setTimeout(resolve, 300));
await focusFirefoxWindow();
Deno.exit(0);
}
logDebug("Failed to open in existing instance, trying alternative method");
// Alternative: try firefox --remote command
if (await runFirefoxCommand(["--remote", `openURL(${url},new-tab)`])) {
logInfo("URL opened using Firefox remote command");
await new Promise(resolve => setTimeout(resolve, 300));
await focusFirefoxWindow();
Deno.exit(0);
}
logDebug("Firefox remote command failed");
} else {
logDebug("No Firefox windows found, starting new instance");
}
// Fallback: start new Firefox instance
logDebug("Starting new Firefox instance");
const firefoxCmd: string[] = [];
// Add profile specification if not default
if (targetProfile !== "default") {
firefoxCmd.push("-P", targetProfile);
}
// Add URL as startup argument
firefoxCmd.push(url);
logDebug(`Executing fallback: firefox ${firefoxCmd.join(" ")}`);
try {
// Start Firefox in background
const cmd = new Deno.Command("firefox", {
args: firefoxCmd,
stdout: "null",
stderr: "null"
});
const _process = cmd.spawn();
// Give Firefox time to start
await new Promise(resolve => setTimeout(resolve, 200));
// Check if process is still running (basic check)
logInfo("Firefox started with new instance");
// Give Firefox more time to fully start, then focus
await new Promise(resolve => setTimeout(resolve, 1000));
await focusFirefoxWindow();
Deno.exit(0);
} catch (error) {
logError("Failed to start Firefox:", error);
Deno.exit(1);
}
}
function showUsage(): void {
console.log(`Usage: firefox-broker <URL>
Routes external links to most recently active Firefox profile.
Environment: FIREFOX_BROKER_DEBUG=1 (default), FIREFOX_BROKER_SILENT=1
Requires: hyprctl, jq, firefox`);
}
// Main execution
async function main(): Promise<void> {
const args = Deno.args;
// Handle help requests
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
showUsage();
Deno.exit(0);
}
// Check dependencies
await checkDependencies();
// Route the URL
await routeUrl(args[0]);
}
// Execute main function
if (import.meta.main) {
main().catch(error => {
logError("Unhandled error:", error);
Deno.exit(1);
});
}
#!/bin/bash
# Firefox Broker Installation Script
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DESKTOP_FILE="$SCRIPT_DIR/firefox-broker.desktop"
VERSION="${1:-bash}"
# Determine which version to install
if [[ "$VERSION" == "deno" ]]; then
BROKER_SCRIPT="$SCRIPT_DIR/firefox-broker.ts"
echo "Installing Firefox Broker (Deno version)..."
# Check if Deno is installed
if ! command -v deno >/dev/null 2>&1; then
echo "Error: Deno is not installed. Please install Deno first."
echo "Visit: https://deno.land/#installation"
exit 1
fi
else
BROKER_SCRIPT="$SCRIPT_DIR/firefox-broker"
echo "Installing Firefox Broker (Bash version)..."
fi
# Validate files exist
[[ -f "$BROKER_SCRIPT" ]] || { echo "Error: $BROKER_SCRIPT not found"; exit 1; }
[[ -f "$DESKTOP_FILE" ]] || { echo "Error: firefox-broker.desktop not found"; exit 1; }
# Install script to PATH
if [[ -d "$HOME/.local/bin" ]]; then
BIN_DIR="$HOME/.local/bin"
else
mkdir -p "$HOME/.local/bin"
BIN_DIR="$HOME/.local/bin"
fi
if [[ "$VERSION" == "deno" ]]; then
# For Deno version, copy the TypeScript file
cp "$BROKER_SCRIPT" "$BIN_DIR/firefox-broker"
# Create a shell wrapper for the Deno script
cat > "$BIN_DIR/firefox-broker" << 'EOF'
#!/bin/bash
exec deno run --allow-all "$(dirname "$0")/firefox-broker.ts" "$@"
EOF
cp "$BROKER_SCRIPT" "$BIN_DIR/firefox-broker.ts"
chmod +x "$BIN_DIR/firefox-broker"
chmod +x "$BIN_DIR/firefox-broker.ts"
else
cp "$BROKER_SCRIPT" "$BIN_DIR/"
chmod +x "$BIN_DIR/firefox-broker"
fi
# Install desktop file
APPS_DIR="$HOME/.local/share/applications"
mkdir -p "$APPS_DIR"
cp "$DESKTOP_FILE" "$APPS_DIR/"
# Update desktop database if available
command -v update-desktop-database >/dev/null 2>&1 && \
update-desktop-database "$APPS_DIR" || true
# Set as default browser
if xdg-settings set default-web-browser firefox-broker.desktop 2>/dev/null; then
echo "✓ Firefox Broker installed and set as default browser"
else
echo "✓ Firefox Broker installed (set as default manually if needed)"
fi
echo
echo "Usage: External links will now route to your active Firefox profile"
echo "Debug: FIREFOX_BROKER_DEBUG=0 firefox-broker https://example.com"
echo
echo "To uninstall:"
if [[ "$VERSION" == "deno" ]]; then
echo " rm $BIN_DIR/firefox-broker $BIN_DIR/firefox-broker.ts $APPS_DIR/firefox-broker.desktop"
else
echo " rm $BIN_DIR/firefox-broker $APPS_DIR/firefox-broker.desktop"
fi
echo " xdg-settings set default-web-browser firefox.desktop"
echo
echo "Installation type: $VERSION"
echo "To install a different version: $0 [bash|deno]"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment