#!/bin/bash # TALLY Link installer for macOS. # curl -fsSL https://tallymeter.io/link/install.sh | bash # Installs a small background helper in your own account (no admin password needed) that reads the # Claude and OpenAI (Codex) usage already on this Mac and sends it to TALLY over the USB-C cable. # (c) 2026 Meta Studios Creative Agency, Tokyo set -u BASE_URL="${TALLY_LINK_URL:-https://tallymeter.io/link}" DEST="$HOME/.tally-link" LABEL="com.metastudios.tally-link" PLIST="$HOME/Library/LaunchAgents/$LABEL.plist" SVC="gui/$(id -u)/$LABEL" TMP="$(mktemp -d "${TMPDIR:-/tmp}/tally-link.XXXXXX")" trap 'rm -rf "$TMP"' EXIT say() { printf '%s\n' "$*"; } step() { printf '\n \033[38;5;173m%s\033[0m\n' "$*"; } fail() { printf '\n \033[31mSomething went wrong:\033[0m %s\n\n' "$*"; exit 1; } say "" say " TALLY Link for Mac" say " ==================" [ "$(uname -s)" = "Darwin" ] || fail "this installer is for macOS. On Windows use: irm https://tallymeter.io/link/install.ps1 | iex" # 1. download and check it is exactly the file we published step "Downloading TALLY Link..." curl -fsSL "$BASE_URL/manifest.json" -o "$TMP/manifest.json" || fail "could not reach $BASE_URL (is the Mac online?)" curl -fsSL "$BASE_URL/tally_link.py" -o "$TMP/tally_link.py" || fail "could not download tally_link.py" WANT="$(sed -n 's/.*"sha256"[[:space:]]*:[[:space:]]*"\([0-9a-f]\{64\}\)".*/\1/p' "$TMP/manifest.json" | head -n 1)" VER="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$TMP/manifest.json" | head -n 1)" GOT="$(shasum -a 256 "$TMP/tally_link.py" | awk '{print $1}')" [ -n "$WANT" ] && [ "$WANT" = "$GOT" ] || fail "the download did not match its checksum. Please try again in a minute." say " version $VER, checksum OK" # 2. a Python 3 to run it (Apple's, Homebrew's or PlatformIO's) step "Looking for Python 3..." PY="" if xcode-select -p >/dev/null 2>&1 && /usr/bin/python3 -c "import venv" >/dev/null 2>&1; then PY=/usr/bin/python3; fi for c in /opt/homebrew/bin/python3 /usr/local/bin/python3 "$HOME/.platformio/penv/bin/python"; do if [ -z "$PY" ] && [ -x "$c" ] && "$c" -c "import venv" >/dev/null 2>&1; then PY="$c"; fi done if [ -z "$PY" ]; then xcode-select --install >/dev/null 2>&1 fail "Python 3 is needed. A window now offers Apple's free command line tools: click Install, wait until it finishes, then run this command again." fi say " $PY" # 3. its own private environment with the USB serial library step "Setting up (one minute the first time)..." mkdir -p "$DEST" "$HOME/Library/LaunchAgents" "$HOME/Library/Logs" if [ ! -x "$DEST/venv/bin/python" ]; then "$PY" -m venv "$DEST/venv" || fail "could not create TALLY Link's Python environment." fi "$DEST/venv/bin/python" -m pip install -q --disable-pip-version-check --upgrade pip >/dev/null 2>&1 "$DEST/venv/bin/python" -m pip install -q --disable-pip-version-check pyserial certifi || fail "could not install pyserial (internet is needed once)." "$DEST/venv/bin/python" -m pip install -q --disable-pip-version-check cramjam >/dev/null 2>&1 || say " (optional speed-up skipped; TALLY Link works without it)" # 4. stop the running copy, put the new one in place (the old one is kept as a backup) launchctl bootout "$SVC" >/dev/null 2>&1 for _ in $(seq 1 30); do launchctl print "$SVC" >/dev/null 2>&1 || break; sleep 0.5; done if [ -f "$DEST/tally_link.py" ]; then OLDV="$(grep -m1 '^VERSION' "$DEST/tally_link.py" | cut -d'"' -f2)" [ -n "$OLDV" ] && [ "$OLDV" != "$VER" ] && cp "$DEST/tally_link.py" "$DEST/tally_link_$OLDV.py" fi cp "$TMP/tally_link.py" "$DEST/tally_link.py" if [ ! -f "$DEST/config.json" ]; then cat > "$DEST/config.json" < "$PLIST" < Label$LABEL ProgramArguments $DEST/venv/bin/python $DEST/tally_link.py RunAtLoad KeepAlive ProcessTypeInteractive StandardOutPath$HOME/Library/Logs/TALLY-Link.out.log StandardErrorPath$HOME/Library/Logs/TALLY-Link.err.log PL for _ in 1 2 3 4 5; do launchctl bootstrap "gui/$(id -u)" "$PLIST" >/dev/null 2>&1 && break launchctl print "$SVC" >/dev/null 2>&1 && break sleep 2 done launchctl kickstart -k "$SVC" >/dev/null 2>&1 RUNNING=0 for _ in $(seq 1 20); do launchctl print "$SVC" 2>/dev/null | grep -q "state = running" && { RUNNING=1; break; } sleep 0.5 done if [ $RUNNING -eq 1 ]; then say " TALLY Link $VER is running, and starts by itself every time you log in." else say " Installed, but macOS hasn't started it yet. Run this command again, or restart the Mac." fi # 6. what it can see step "What TALLY Link can read on this Mac:" "$DEST/venv/bin/python" "$DEST/tally_link.py" --status 2>/dev/null | sed 's/^/ /' say "" say " Done. Plug TALLY in with a USB-C data cable: your usage appears within about 20 seconds." say " Claude shows 'not found'? Open the Claude desktop app once, or connect live Claude usage with:" say " curl -fsSL $BASE_URL/connect-claude.sh | bash" say " Remove TALLY Link any time with:" say " curl -fsSL $BASE_URL/uninstall.sh | bash" say ""