`.
+output "scripts" {
+ description = "Ordered list of coder exp sync names for the coder_script resources this module actually creates, in run order (pre_install, install, post_install, start). Scripts that were not configured are absent from the list."
+ value = module.coder_utils.scripts
+}
diff --git a/registry/gojnimer6553/modules/happy/main.tftest.hcl b/registry/gojnimer6553/modules/happy/main.tftest.hcl
new file mode 100644
index 000000000..d8f064348
--- /dev/null
+++ b/registry/gojnimer6553/modules/happy/main.tftest.hcl
@@ -0,0 +1,314 @@
+run "defaults_are_correct" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ }
+
+ assert {
+ condition = var.port == 4020
+ error_message = "Default port should be 4020"
+ }
+
+ assert {
+ condition = var.install == true
+ error_message = "Happy installation should be enabled by default"
+ }
+
+ assert {
+ condition = var.install_version == "latest"
+ error_message = "Default install_version should be 'latest'"
+ }
+
+ assert {
+ condition = var.package_manager == "npm"
+ error_message = "Default package_manager should be 'npm'"
+ }
+
+ assert {
+ condition = var.use_cached == false
+ error_message = "use_cached should be disabled by default"
+ }
+
+ assert {
+ condition = var.share == "owner"
+ error_message = "Default share should be 'owner'"
+ }
+
+ assert {
+ condition = var.subdomain == true
+ error_message = "subdomain should be enabled by default"
+ }
+
+ assert {
+ condition = var.tmux_session == "happy"
+ error_message = "Default tmux_session should be 'happy'"
+ }
+
+ assert {
+ condition = local.module_dir_name == ".coder-modules/gojnimer6553/happy"
+ error_message = "Module dir name should be '.coder-modules/gojnimer6553/happy'"
+ }
+
+ assert {
+ condition = local.install_prefix_override == ""
+ error_message = "install_prefix_override should be empty by default (the script computes the $HOME-based default itself)"
+ }
+
+ assert {
+ condition = local.workdir_override == ""
+ error_message = "workdir_override should be empty by default (the script defaults to $HOME itself)"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.url == "http://localhost:4020"
+ error_message = "App URL should point at localhost on the configured port"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.slug == "happy"
+ error_message = "Default slug should be 'happy'"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.share == "owner"
+ error_message = "Default share should be 'owner'"
+ }
+}
+
+run "custom_port_configuration" {
+ command = apply
+
+ variables {
+ agent_id = "test-agent"
+ port = 4021
+ }
+
+ assert {
+ condition = resource.coder_app.happy.url == "http://localhost:4021"
+ error_message = "App URL should use the configured port"
+ }
+
+ assert {
+ condition = [for h in resource.coder_app.happy.healthcheck : h.url][0] == "http://localhost:4021/healthz"
+ error_message = "Healthcheck URL should use the configured port"
+ }
+}
+
+run "custom_workdir_configuration" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ workdir = "/home/coder/project"
+ }
+
+ assert {
+ condition = local.workdir_override == "/home/coder/project"
+ error_message = "Custom workdir should be forwarded as the override"
+ }
+}
+
+run "custom_install_prefix_overrides_default" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ install_prefix = "/opt/happy"
+ }
+
+ assert {
+ condition = local.install_prefix_override == "/opt/happy"
+ error_message = "Explicit install_prefix should be forwarded as the override"
+ }
+}
+
+run "install_version_configuration" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ install_version = "1.2.0"
+ }
+
+ assert {
+ condition = var.install_version == "1.2.0"
+ error_message = "install_version should be set correctly"
+ }
+}
+
+run "invalid_package_manager_rejected" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ package_manager = "yarn"
+ }
+
+ expect_failures = [
+ var.package_manager,
+ ]
+}
+
+run "invalid_share_rejected" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ share = "invalid"
+ }
+
+ expect_failures = [
+ var.share,
+ ]
+}
+
+run "invalid_open_in_rejected" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ open_in = "invalid"
+ }
+
+ expect_failures = [
+ var.open_in,
+ ]
+}
+
+run "custom_share_configuration" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ share = "public"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.share == "public"
+ error_message = "Custom share should override the default 'owner'"
+ }
+}
+
+run "subdomain_disabled_configuration" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ subdomain = false
+ }
+
+ assert {
+ condition = resource.coder_app.happy.subdomain == false
+ error_message = "subdomain should be disabled when specified"
+ }
+}
+
+run "custom_display_and_slug" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ slug = "happy-cli"
+ display_name = "Happy"
+ icon = "/custom/icon.svg"
+ order = 5
+ group = "AI Tools"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.slug == "happy-cli"
+ error_message = "Custom slug should be set"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.display_name == "Happy"
+ error_message = "Custom display_name should be set"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.icon == "/custom/icon.svg"
+ error_message = "Custom icon should be set"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.order == 5
+ error_message = "Custom order should be set"
+ }
+
+ assert {
+ condition = resource.coder_app.happy.group == "AI Tools"
+ error_message = "Custom group should be set"
+ }
+}
+
+run "custom_scripts_configuration" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ pre_install_script = "#!/bin/bash\necho 'pre-install'"
+ post_install_script = "#!/bin/bash\necho 'post-install'"
+ }
+
+ assert {
+ condition = var.pre_install_script != null
+ error_message = "Pre-install script should be set"
+ }
+
+ assert {
+ condition = var.post_install_script != null
+ error_message = "Post-install script should be set"
+ }
+
+ assert {
+ condition = can(regex("pre-install", var.pre_install_script))
+ error_message = "Pre-install script should contain expected content"
+ }
+
+ assert {
+ condition = can(regex("post-install", var.post_install_script))
+ error_message = "Post-install script should contain expected content"
+ }
+}
+
+run "install_disabled_configuration" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ install = false
+ }
+
+ assert {
+ condition = var.install == false
+ error_message = "install should be disabled when specified"
+ }
+}
+
+run "custom_tmux_session_configuration" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ tmux_session = "happy-custom"
+ }
+
+ assert {
+ condition = var.tmux_session == "happy-custom"
+ error_message = "tmux_session should be set correctly"
+ }
+}
+
+run "scripts_output_is_populated" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ }
+
+ assert {
+ condition = length(output.scripts) > 0
+ error_message = "scripts output should list at least the install and start scripts"
+ }
+}
diff --git a/registry/gojnimer6553/modules/happy/scripts/install.sh.tftpl b/registry/gojnimer6553/modules/happy/scripts/install.sh.tftpl
new file mode 100644
index 000000000..f32828bc2
--- /dev/null
+++ b/registry/gojnimer6553/modules/happy/scripts/install.sh.tftpl
@@ -0,0 +1,187 @@
+#!/bin/bash
+
+set -euo pipefail
+
+BOLD='\033[0;1m'
+
+command_exists() {
+ command -v "$1" > /dev/null 2>&1
+}
+
+ARG_INSTALL='${ARG_INSTALL}'
+ARG_USE_CACHED='${ARG_USE_CACHED}'
+ARG_VERSION='${ARG_VERSION}'
+ARG_PACKAGE_MANAGER='${ARG_PACKAGE_MANAGER}'
+ARG_REGISTRY_URL='${ARG_REGISTRY_URL}'
+ARG_NODE_VERSION='${ARG_NODE_VERSION}'
+ARG_MODULE_DIR_NAME='${ARG_MODULE_DIR_NAME}'
+# Raw override only ("" means "no override"); the "$HOME"-based default is
+# built below from a real shell variable, not embedded as literal "$HOME"
+# text inside a double-quoted assignment of caller-supplied content -- see
+# the comment on install_prefix_override in main.tf for why that matters.
+ARG_INSTALL_PREFIX_OVERRIDE='${ARG_INSTALL_PREFIX_OVERRIDE}'
+
+if [ -n "$${ARG_INSTALL_PREFIX_OVERRIDE}" ]; then
+ INSTALL_PREFIX="$${ARG_INSTALL_PREFIX_OVERRIDE}"
+else
+ INSTALL_PREFIX="$HOME/$${ARG_MODULE_DIR_NAME}/npm"
+fi
+
+echo "--------------------------------"
+printf "ARG_INSTALL: %s\n" "$${ARG_INSTALL}"
+printf "ARG_USE_CACHED: %s\n" "$${ARG_USE_CACHED}"
+printf "ARG_VERSION: %s\n" "$${ARG_VERSION}"
+printf "ARG_PACKAGE_MANAGER: %s\n" "$${ARG_PACKAGE_MANAGER}"
+printf "ARG_REGISTRY_URL: %s\n" "$${ARG_REGISTRY_URL}"
+printf "ARG_NODE_VERSION: %s\n" "$${ARG_NODE_VERSION}"
+printf "INSTALL_PREFIX: %s\n" "$INSTALL_PREFIX"
+echo "--------------------------------"
+
+# The pairing page (started by start.sh) drives happy's interactive
+# auth-method selector through a real pseudo-terminal, since happy has no
+# non-interactive pairing path (it renders an Ink UI and calls into raw
+# terminal mode). tmux is how that automation observes and drives it
+# (send-keys/capture output). Unlike Node, tmux has no official portable
+# static binary this script can drop into $HOME without privileges -- it
+# links against system libraries (libevent, ncurses) via the platform's
+# package manager. Best-effort install it that way when root or passwordless
+# sudo is available; otherwise fail with a clear message instead of a
+# confusing mid-script permissions error.
+ensure_tmux() {
+ if command_exists tmux; then
+ return 0
+ fi
+
+ echo "⚠️ tmux not found on PATH; attempting to install it..."
+
+ local as_root=""
+ if [ "$(id -u)" != "0" ]; then
+ if command_exists sudo && sudo -n true > /dev/null 2>&1; then
+ as_root="sudo -n"
+ else
+ echo "❌ tmux is required to run Happy (it drives happy's interactive pairing prompt through a real pseudo-terminal) but was not found on PATH, and this workspace has neither root nor passwordless sudo to install it automatically. Add tmux to the workspace image."
+ exit 1
+ fi
+ fi
+
+ if command_exists apt-get; then
+ $as_root apt-get update -qq || true
+ $as_root apt-get install -y -qq tmux || true
+ elif command_exists dnf; then
+ $as_root dnf install -y -q tmux || true
+ elif command_exists yum; then
+ $as_root yum install -y -q tmux || true
+ elif command_exists apk; then
+ $as_root apk add --no-cache tmux || true
+ elif command_exists pacman; then
+ $as_root pacman -Sy --noconfirm tmux || true
+ fi
+
+ if ! command_exists tmux; then
+ echo "❌ tmux is required to run Happy (it drives happy's interactive pairing prompt through a real pseudo-terminal) but was not found on PATH, and automatic installation failed (no supported package manager found, or the install itself failed). Add tmux to the workspace image manually."
+ exit 1
+ fi
+
+ echo "✅ tmux installed automatically."
+}
+
+ensure_tmux
+
+if [ "$${ARG_INSTALL}" != "true" ]; then
+ echo "⏭️ install=false; skipping Happy installation."
+ exit 0
+fi
+
+# Happy (the "happy" npm package) is a Node.js application and needs a
+# Node.js runtime. When the workspace image does not provide one, bootstrap
+# a pinned runtime into $HOME so it persists across restarts.
+ensure_node() {
+ if command_exists node; then
+ return 0
+ fi
+
+ local node_arch node_dir
+ case "$(uname -m)" in
+ x86_64 | amd64) node_arch="x64" ;;
+ aarch64 | arm64) node_arch="arm64" ;;
+ *)
+ echo "❌ node is required to run Happy but was not found on PATH, and automatic bootstrap does not support architecture '$(uname -m)'."
+ exit 1
+ ;;
+ esac
+
+ node_dir="$HOME/$${ARG_MODULE_DIR_NAME}/node/node-v$${ARG_NODE_VERSION}-linux-$${node_arch}"
+ if [ ! -x "$node_dir/bin/node" ]; then
+ echo "⚠️ node not found on PATH; bootstrapping Node.js v$${ARG_NODE_VERSION} into $node_dir..."
+ mkdir -p "$(dirname "$node_dir")"
+ if ! curl -fsSL "https://nodejs.org/dist/v$${ARG_NODE_VERSION}/node-v$${ARG_NODE_VERSION}-linux-$${node_arch}.tar.gz" | tar -xz -C "$(dirname "$node_dir")"; then
+ echo "❌ Failed to download Node.js v$${ARG_NODE_VERSION}. Happy cannot run without node."
+ exit 1
+ fi
+ fi
+
+ export PATH="$node_dir/bin:$PATH"
+
+ # Expose node/npm to other workspace scripts and interactive shells.
+ if [ -n "$${CODER_SCRIPT_BIN_DIR:-}" ]; then
+ [ -e "$${CODER_SCRIPT_BIN_DIR}/node" ] || ln -s "$node_dir/bin/node" "$${CODER_SCRIPT_BIN_DIR}/node"
+ [ -e "$${CODER_SCRIPT_BIN_DIR}/npm" ] || ln -s "$node_dir/bin/npm" "$${CODER_SCRIPT_BIN_DIR}/npm"
+ fi
+}
+
+ensure_node
+
+HAPPY_BIN="$INSTALL_PREFIX/node_modules/.bin/happy"
+
+if [ -x "$HAPPY_BIN" ] && [ "$${ARG_USE_CACHED}" = "true" ]; then
+ printf "%s\n" "🥳 Found a cached copy of Happy, skipping install (use_cached=true)"
+ "$HAPPY_BIN" --version || true
+ exit 0
+fi
+
+case "$${ARG_PACKAGE_MANAGER}" in
+ npm | pnpm | bun) ;;
+ *)
+ echo "❌ Unsupported package_manager '$${ARG_PACKAGE_MANAGER}'. Use 'npm', 'pnpm', or 'bun'."
+ exit 1
+ ;;
+esac
+
+if ! command_exists "$${ARG_PACKAGE_MANAGER}"; then
+ echo "❌ package_manager '$${ARG_PACKAGE_MANAGER}' was not found on PATH."
+ exit 1
+fi
+
+mkdir -p "$INSTALL_PREFIX"
+cd "$INSTALL_PREFIX"
+[ -f package.json ] || echo '{"private":true}' > package.json
+
+# "qrcode" renders the pairing link captured from happy's terminal output as
+# a real scannable image for the pairing page (start.sh.tftpl / pairing
+# server), instead of trying to re-parse happy's own ANSI QR art.
+HAPPY_SPEC="happy@$${ARG_VERSION}"
+QRCODE_SPEC="qrcode@1.5.4"
+
+# %s arguments, not a %-interpreted format string: HAPPY_SPEC embeds
+# ARG_VERSION, a caller-supplied string that could contain a literal "%".
+printf "%sInstalling Happy (%s) and qrcode via %s...\n" "$${BOLD}" "$${HAPPY_SPEC}" "$${ARG_PACKAGE_MANAGER}"
+
+case "$${ARG_PACKAGE_MANAGER}" in
+ npm)
+ npm install --no-audit --no-fund --registry "$${ARG_REGISTRY_URL}" "$${HAPPY_SPEC}" "$${QRCODE_SPEC}"
+ ;;
+ pnpm)
+ pnpm add --registry "$${ARG_REGISTRY_URL}" "$${HAPPY_SPEC}" "$${QRCODE_SPEC}"
+ ;;
+ bun)
+ bun add --registry "$${ARG_REGISTRY_URL}" "$${HAPPY_SPEC}" "$${QRCODE_SPEC}"
+ ;;
+esac
+
+if [ ! -x "$HAPPY_BIN" ]; then
+ echo "❌ Could not locate the happy binary after install at $HAPPY_BIN"
+ exit 1
+fi
+
+printf "%s\n\n" "🥳 Happy has been installed to $INSTALL_PREFIX"
+"$HAPPY_BIN" --version || true
diff --git a/registry/gojnimer6553/modules/happy/scripts/start.sh.tftpl b/registry/gojnimer6553/modules/happy/scripts/start.sh.tftpl
new file mode 100644
index 000000000..dba43680c
--- /dev/null
+++ b/registry/gojnimer6553/modules/happy/scripts/start.sh.tftpl
@@ -0,0 +1,278 @@
+#!/bin/bash
+
+set -euo pipefail
+
+ARG_PORT='${ARG_PORT}'
+ARG_TMUX_SESSION='${ARG_TMUX_SESSION}'
+# module_directory is not caller-influenced (module_dir_name is a hardcoded
+# constant in main.tf), so double-quoting it to expand its literal "$HOME"
+# at runtime is safe. install_prefix/workdir are caller-supplied though --
+# see the comment on install_prefix_override in main.tf for why those are
+# passed as raw overrides instead and built into a full path below using a
+# real, un-interpolated shell variable rather than embedded "$HOME" text.
+ARG_MODULE_DIRECTORY="${ARG_MODULE_DIRECTORY}"
+ARG_INSTALL_PREFIX_OVERRIDE='${ARG_INSTALL_PREFIX_OVERRIDE}'
+ARG_WORKDIR_OVERRIDE='${ARG_WORKDIR_OVERRIDE}'
+
+if [ -n "$${ARG_INSTALL_PREFIX_OVERRIDE}" ]; then
+ INSTALL_PREFIX="$${ARG_INSTALL_PREFIX_OVERRIDE}"
+else
+ INSTALL_PREFIX="$${ARG_MODULE_DIRECTORY}/npm"
+fi
+
+if [ -n "$${ARG_WORKDIR_OVERRIDE}" ]; then
+ WORKDIR="$${ARG_WORKDIR_OVERRIDE}"
+else
+ WORKDIR="$HOME"
+fi
+
+HAPPY_BIN="$INSTALL_PREFIX/node_modules/.bin/happy"
+SCRIPTS_DIR="$${ARG_MODULE_DIRECTORY}/scripts"
+LOG_PATH="$${ARG_MODULE_DIRECTORY}/logs/start.log"
+PAIRING_LOG_PATH="$${ARG_MODULE_DIRECTORY}/logs/pairing.log"
+SERVER_PATH="$SCRIPTS_DIR/pairing-server.js"
+
+if [ ! -x "$HAPPY_BIN" ]; then
+ echo "❌ happy binary not found at $HAPPY_BIN. Did the install script run and succeed?"
+ exit 1
+fi
+
+if ! command -v tmux > /dev/null 2>&1; then
+ echo "❌ tmux not found on PATH. Did the install script run and succeed?"
+ exit 1
+fi
+
+mkdir -p "$SCRIPTS_DIR" "$(dirname "$LOG_PATH")"
+mkdir -p "$WORKDIR"
+: > "$PAIRING_LOG_PATH"
+
+# Pairing server: on every request to "/" it forces a completely fresh
+# pairing cycle (kill any previous happy session, clear credentials, relaunch,
+# drive the interactive auth-method prompt via tmux, capture the resulting
+# happy://terminal?... link, and render it as a real QR code). "/healthz"
+# never triggers this -- it must stay side-effect-free since Coder polls it
+# on an interval, and forcing a re-pair every few seconds would make the
+# module unusable. Node-only (plus the "qrcode" package installed alongside
+# happy): a Node runtime is already required to run Happy itself.
+cat > "$SERVER_PATH" << 'EOF_SERVER'
+"use strict";
+const http = require("http");
+const fs = require("fs");
+const { spawnSync } = require("child_process");
+const QRCode = require("qrcode");
+
+const PORT = parseInt(process.env.PORT, 10);
+const HAPPY_BIN = process.env.HAPPY_BIN;
+const WORKDIR = process.env.WORKDIR;
+const TMUX_SESSION = process.env.TMUX_SESSION;
+const PAIRING_LOG_PATH = process.env.PAIRING_LOG_PATH;
+
+const PAIRING_URL_RE = /happy:\/\/terminal\?[A-Za-z0-9_-]+/;
+const PROMPT_TEXT = "How would you like to authenticate?";
+const PROMPT_TIMEOUT_MS = 20000;
+const PAIRING_URL_TIMEOUT_MS = 20000;
+const POLL_INTERVAL_MS = 300;
+
+function shellQuote(value) {
+ return "'" + String(value).replace(/'/g, "'\\''") + "'";
+}
+
+// Polls the pane's *current* on-screen content, not the piped log file.
+// "tmux pipe-pane" (attached separately, after the session already starts)
+// only forwards output emitted after it attaches; if the wrapped process
+// prints before that attach call lands, that output is lost from the log
+// forever and a log-based wait hangs until it times out. capture-pane has
+// no such race: it always returns whatever is presently on screen,
+// regardless of when polling started.
+function capturePane() {
+ const result = spawnSync("tmux", ["capture-pane", "-t", TMUX_SESSION, "-p", "-S", "-200"]);
+ return result.status === 0 ? String(result.stdout) : "";
+}
+
+function sessionAlive() {
+ const result = spawnSync("tmux", ["has-session", "-t", TMUX_SESSION]);
+ return result.status === 0;
+}
+
+// Without this, a happy CLI crash/exit right after the prompt (the tmux
+// session dies once its wrapped shell command finishes) would otherwise
+// just look identical to "still starting" and wait out the full timeout
+// before failing -- this fails fast instead, with the pane's last output
+// attached so the real cause is visible immediately.
+function waitFor(extract, timeoutMs, timeoutMessage) {
+ const deadline = Date.now() + timeoutMs;
+ return new Promise((resolve, reject) => {
+ const tick = () => {
+ const paneText = capturePane();
+ const found = extract(paneText);
+ if (found) {
+ resolve(found);
+ return;
+ }
+ if (!sessionAlive()) {
+ reject(
+ new Error(
+ "Happy's tmux session exited before it finished pairing. Last output:\n" + paneText,
+ ),
+ );
+ return;
+ }
+ if (Date.now() >= deadline) {
+ reject(new Error(timeoutMessage));
+ return;
+ }
+ setTimeout(tick, POLL_INTERVAL_MS);
+ };
+ tick();
+ });
+}
+
+function startFreshPairingCycle() {
+ // Best-effort teardown of whatever was running before.
+ spawnSync("tmux", ["kill-session", "-t", TMUX_SESSION]);
+ fs.writeFileSync(PAIRING_LOG_PATH, "");
+
+ // "auth login --force" clears any existing credentials/machine ID and
+ // stops the daemon, then (since credentials are now gone) drives straight
+ // into the same interactive auth-method prompt as a first-ever pairing.
+ // Only once that finishes does "happy claude" run, at which point
+ // credentials already exist so it launches straight into the wrapped
+ // coding session with no further prompts.
+ const shellCommand =
+ "cd " + shellQuote(WORKDIR) + " && " +
+ shellQuote(HAPPY_BIN) + " auth login --force && " +
+ shellQuote(HAPPY_BIN) + " claude";
+
+ const spawned = spawnSync("tmux", [
+ "new-session", "-d", "-s", TMUX_SESSION, "-x", "220", "-y", "50",
+ "bash", "-lc", shellCommand,
+ ]);
+ if (spawned.status !== 0) {
+ throw new Error(
+ "Failed to start tmux session: " + (spawned.stderr || spawned.error || "unknown error"),
+ );
+ }
+
+ spawnSync("tmux", ["pipe-pane", "-t", TMUX_SESSION, "-o", "cat >> " + shellQuote(PAIRING_LOG_PATH)]);
+}
+
+async function runPairingCycle() {
+ startFreshPairingCycle();
+ await waitFor(
+ (text) => text.includes(PROMPT_TEXT),
+ PROMPT_TIMEOUT_MS,
+ "Timed out waiting for Happy's auth-method prompt to appear",
+ );
+ spawnSync("tmux", ["send-keys", "-t", TMUX_SESSION, "1"]);
+ const pairingUrl = await waitFor(
+ (text) => {
+ const match = PAIRING_URL_RE.exec(text);
+ return match ? match[0] : null;
+ },
+ PAIRING_URL_TIMEOUT_MS,
+ "Timed out waiting for Happy to print a pairing link",
+ );
+ // Detach the pipe once pairing itself is done: from here on "happy claude"
+ // runs the actual coding session, and there's no reason to keep mirroring
+ // that (potentially sensitive) transcript to a plaintext log file on top
+ // of it already living in the tmux scrollback.
+ spawnSync("tmux", ["pipe-pane", "-t", TMUX_SESSION]);
+ return pairingUrl;
+}
+
+let inFlight = null;
+
+function getOrStartPairingCycle() {
+ if (!inFlight) {
+ inFlight = runPairingCycle().finally(() => {
+ inFlight = null;
+ });
+ }
+ return inFlight;
+}
+
+function escapeHtml(value) {
+ return String(value).replace(/[&<>"']/g, (c) => (
+ { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]
+ ));
+}
+
+function htmlPage(bodyHtml, refreshSeconds) {
+ return (
+ "" +
+ (refreshSeconds ? "" : "") +
+ "Happy Pairing" + bodyHtml + ""
+ );
+}
+
+async function renderQrPage(pairingUrl) {
+ const qrDataUrl = await QRCode.toDataURL(pairingUrl, { margin: 2, width: 280 });
+ return htmlPage(
+ "Scan to pair Happy
" +
+ "
" +
+ "Or open this link on your phone:
" +
+ "" + escapeHtml(pairingUrl) + "" +
+ "This link is single-use and was just generated for this click. " +
+ "Re-opening this app mints a new one and disconnects any device already paired.
",
+ 0,
+ );
+}
+
+function renderErrorPage(message) {
+ return htmlPage(
+ "Something went wrong
" + escapeHtml(message) + "
" +
+ "Reload this page to try again.
",
+ 5,
+ );
+}
+
+const server = http.createServer((req, res) => {
+ if (req.method === "GET" && req.url === "/healthz") {
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ res.end("ok");
+ return;
+ }
+
+ // Only an actual "GET /" click should trigger a destructive re-pair.
+ // Without this, incidental requests a browser makes on its own (e.g.
+ // "/favicon.ico") would each kill and restart the pairing cycle too.
+ if (req.method !== "GET" || req.url !== "/") {
+ res.writeHead(404, { "Content-Type": "text/plain" });
+ res.end("not found");
+ return;
+ }
+
+ getOrStartPairingCycle()
+ .then((url) => renderQrPage(url))
+ .then((html) => {
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
+ res.end(html);
+ })
+ .catch((err) => {
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
+ res.end(renderErrorPage(err && err.message ? err.message : String(err)));
+ });
+});
+
+server.listen(PORT, "127.0.0.1");
+EOF_SERVER
+
+echo "🚀 Starting the Happy pairing server on port $${ARG_PORT}..."
+
+nohup env \
+ HAPPY_BIN="$HAPPY_BIN" \
+ PORT="$${ARG_PORT}" \
+ WORKDIR="$WORKDIR" \
+ TMUX_SESSION="$${ARG_TMUX_SESSION}" \
+ PAIRING_LOG_PATH="$PAIRING_LOG_PATH" \
+ NODE_PATH="$INSTALL_PREFIX/node_modules" \
+ node "$SERVER_PATH" >> "$LOG_PATH" 2>&1 &
+disown
+
+echo "Happy pairing server started (pid $!). Logs: $LOG_PATH"
+echo "ℹ️ Open the Happy app to get a QR code / pairing link. Every open mints a fresh one and disconnects any previously paired device."