From 97a1700a8b3c08b62efebdfef74c5138494755d5 Mon Sep 17 00:00:00 2001 From: Plaidmustache <161773990+Plaidmustache@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:59:35 +0100 Subject: [PATCH 1/2] Add macOS launchd auto-start setup --- README.md | 41 ++++++++ claude-launchd-bootstrap.sh | 23 +++++ setup-claude-launchd.sh | 197 ++++++++++++++++++++++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100755 claude-launchd-bootstrap.sh create mode 100755 setup-claude-launchd.sh diff --git a/README.md b/README.md index 0f4ce3b2..86b05abe 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,9 @@ chmod +x *.sh # Interactive setup (recommended) ./setup-claude-cron.sh +# macOS auto-start on login (launchd) +./setup-claude-launchd.sh install + # OR manual daemon start ./claude-daemon-manager.sh start ./claude-daemon-manager.sh start --at "09:00" # with start time @@ -140,6 +143,30 @@ chmod +x *.sh ./claude-daemon-manager.sh restart --at "09:00" --stop "17:00" # new schedule ``` +### macOS Auto-Start With launchd + +For macOS users who want the daemon to start automatically at login: + +```bash +# Install auto-start using your current shell PATH +./setup-claude-launchd.sh install + +# Install auto-start with daemon start arguments +./setup-claude-launchd.sh install -- --at "09:00" --stop "17:00" +./setup-claude-launchd.sh install -- --disableccusage + +# Check launchd + daemon status +./setup-claude-launchd.sh status + +# Remove auto-start +./setup-claude-launchd.sh uninstall +``` + +Notes: +- The launch agent triggers `./claude-daemon-manager.sh start` when you log in. +- It uses the current shell `PATH` when generating the plist, so `claude`, `ccusage`, `npx`, or `bunx` can still be found by launchd. +- If you move those commands to a different location later, run the install command again to refresh the LaunchAgent. + ### Live Dashboard 📊 The new live dashboard provides real-time monitoring of your Claude renewal status: @@ -319,7 +346,9 @@ cc-autorenew/ ├── claude-auto-renew-daemon.sh # Core daemon process ├── claude-auto-renew-advanced.sh # Standalone renewal script ├── claude-auto-renew.sh # Basic renewal script +├── claude-launchd-bootstrap.sh # launchd helper to start the daemon once per login ├── setup-claude-cron.sh # Interactive setup (daemon/cron) +├── setup-claude-launchd.sh # macOS launchd auto-start installer ├── test-start-time-feature.sh # New comprehensive test suite ├── reddit.md # Reddit post about the project └── README.md # This file @@ -362,6 +391,18 @@ The daemon uses smart defaults, but you can modify behavior by editing `claude-a tail -20 ~/.claude-auto-renew-daemon.log ``` +### launchd auto-start not working (macOS) +```bash +# Check launchd + daemon status +./setup-claude-launchd.sh status + +# Reinstall the LaunchAgent using your current shell PATH +./setup-claude-launchd.sh install + +# Check launchd bootstrap logs +tail -20 ~/.claude-auto-renew-launchd.err.log +``` + ### ccusage not working ```bash # Test ccusage directly diff --git a/claude-launchd-bootstrap.sh b/claude-launchd-bootstrap.sh new file mode 100755 index 00000000..8f8971f1 --- /dev/null +++ b/claude-launchd-bootstrap.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# launchd helper: start the daemon if it is not already running. + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +DAEMON_MANAGER="$SCRIPT_DIR/claude-daemon-manager.sh" +PID_FILE="$HOME/.claude-auto-renew-daemon.pid" + +if [ ! -x "$DAEMON_MANAGER" ]; then + echo "ERROR: claude-daemon-manager.sh not found or not executable at $DAEMON_MANAGER" >&2 + exit 1 +fi + +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE" 2>/dev/null) + if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then + exit 0 + fi + + rm -f "$PID_FILE" +fi + +exec "$DAEMON_MANAGER" start "$@" diff --git a/setup-claude-launchd.sh b/setup-claude-launchd.sh new file mode 100755 index 00000000..adc409a3 --- /dev/null +++ b/setup-claude-launchd.sh @@ -0,0 +1,197 @@ +#!/bin/bash + +# Setup script for macOS launchd auto-start. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +DAEMON_MANAGER="$SCRIPT_DIR/claude-daemon-manager.sh" +BOOTSTRAP_SCRIPT="$SCRIPT_DIR/claude-launchd-bootstrap.sh" +LABEL="com.ccautorenew.launchd" +PLIST_DIR="$HOME/Library/LaunchAgents" +PLIST_PATH="$PLIST_DIR/$LABEL.plist" +STDOUT_LOG="$HOME/.claude-auto-renew-launchd.out.log" +STDERR_LOG="$HOME/.claude-auto-renew-launchd.err.log" +LAUNCHD_DOMAIN="gui/$(id -u)" +ACTION="install" +EXTRA_ARGS=() + +print_help() { + cat <<'EOF' +Usage: + ./setup-claude-launchd.sh install [-- daemon start args...] + ./setup-claude-launchd.sh status + ./setup-claude-launchd.sh uninstall + +Examples: + ./setup-claude-launchd.sh install + ./setup-claude-launchd.sh install -- --at "09:00" --stop "17:00" + ./setup-claude-launchd.sh install -- --disableccusage + ./setup-claude-launchd.sh status + ./setup-claude-launchd.sh uninstall + +Notes: + - This is for macOS only. + - The launch agent runs at login and triggers the normal daemon manager. + - Any arguments after -- are passed to: + ./claude-daemon-manager.sh start +EOF +} + +xml_escape() { + local value="$1" + value=${value//&/&} + value=${value///>} + value=${value//\"/"} + value=${value//\'/'} + printf '%s' "$value" +} + +ensure_supported() { + if [ "$(uname -s)" != "Darwin" ]; then + echo "ERROR: launchd setup is only supported on macOS." + exit 1 + fi + + if [ ! -x "$DAEMON_MANAGER" ]; then + echo "ERROR: claude-daemon-manager.sh not found or not executable at $DAEMON_MANAGER" + exit 1 + fi + + if [ ! -x "$BOOTSTRAP_SCRIPT" ]; then + echo "ERROR: claude-launchd-bootstrap.sh not found or not executable at $BOOTSTRAP_SCRIPT" + exit 1 + fi +} + +is_loaded() { + launchctl print "$LAUNCHD_DOMAIN/$LABEL" >/dev/null 2>&1 +} + +write_plist() { + mkdir -p "$PLIST_DIR" + + { + printf '%s\n' '' + printf '%s\n' '' + printf '%s\n' '' + printf '%s\n' '' + printf ' Label\n' + printf ' %s\n' "$(xml_escape "$LABEL")" + printf '\n' + printf ' ProgramArguments\n' + printf ' \n' + printf ' %s\n' "$(xml_escape "$BOOTSTRAP_SCRIPT")" + + if [ ${#EXTRA_ARGS[@]} -gt 0 ]; then + for arg in "${EXTRA_ARGS[@]}"; do + printf ' %s\n' "$(xml_escape "$arg")" + done + fi + + printf ' \n' + printf '\n' + printf ' WorkingDirectory\n' + printf ' %s\n' "$(xml_escape "$SCRIPT_DIR")" + printf '\n' + printf ' EnvironmentVariables\n' + printf ' \n' + printf ' PATH\n' + printf ' %s\n' "$(xml_escape "$PATH")" + printf ' \n' + printf '\n' + printf ' RunAtLoad\n' + printf ' \n' + printf '\n' + printf ' StandardOutPath\n' + printf ' %s\n' "$(xml_escape "$STDOUT_LOG")" + printf ' StandardErrorPath\n' + printf ' %s\n' "$(xml_escape "$STDERR_LOG")" + printf '%s\n' '' + printf '%s\n' '' + } > "$PLIST_PATH" +} + +install_launchd() { + write_plist + + launchctl bootout "$LAUNCHD_DOMAIN" "$PLIST_PATH" >/dev/null 2>&1 || true + launchctl bootstrap "$LAUNCHD_DOMAIN" "$PLIST_PATH" + launchctl kickstart -k "$LAUNCHD_DOMAIN/$LABEL" >/dev/null 2>&1 || true + + echo "✅ launchd auto-start installed" + echo "Plist: $PLIST_PATH" + echo "The daemon will be started automatically when you log in." + echo "" + "$DAEMON_MANAGER" status || true +} + +uninstall_launchd() { + launchctl bootout "$LAUNCHD_DOMAIN" "$PLIST_PATH" >/dev/null 2>&1 || true + rm -f "$PLIST_PATH" + + echo "✅ launchd auto-start removed" + echo "The daemon itself is not stopped automatically." + echo "Use ./claude-daemon-manager.sh stop if you also want to stop the current daemon." +} + +status_launchd() { + if [ -f "$PLIST_PATH" ]; then + echo "LaunchAgent installed: $PLIST_PATH" + else + echo "LaunchAgent installed: no" + fi + + if is_loaded; then + echo "LaunchAgent loaded: yes" + else + echo "LaunchAgent loaded: no" + fi + + echo "" + "$DAEMON_MANAGER" status || true +} + +while [ $# -gt 0 ]; do + case "$1" in + install|status|uninstall) + ACTION="$1" + shift + ;; + help|-h|--help) + ACTION="help" + shift + ;; + --) + shift + EXTRA_ARGS=("$@") + break + ;; + *) + echo "ERROR: Unknown argument: $1" + echo "" + print_help + exit 1 + ;; + esac +done + +if [ "$ACTION" = "help" ]; then + print_help + exit 0 +fi + +ensure_supported + +case "$ACTION" in + install) + install_launchd + ;; + status) + status_launchd + ;; + uninstall) + uninstall_launchd + ;; +esac From 5b48b39c00d560c28c57dae812fd0e82e4a29401 Mon Sep 17 00:00:00 2001 From: Plaidmustache <161773990+Plaidmustache@users.noreply.github.com> Date: Sat, 28 Feb 2026 18:25:13 +0100 Subject: [PATCH 2/2] Avoid redundant launchd kickstart --- setup-claude-launchd.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/setup-claude-launchd.sh b/setup-claude-launchd.sh index adc409a3..8bfefcfc 100755 --- a/setup-claude-launchd.sh +++ b/setup-claude-launchd.sh @@ -118,7 +118,6 @@ install_launchd() { launchctl bootout "$LAUNCHD_DOMAIN" "$PLIST_PATH" >/dev/null 2>&1 || true launchctl bootstrap "$LAUNCHD_DOMAIN" "$PLIST_PATH" - launchctl kickstart -k "$LAUNCHD_DOMAIN/$LABEL" >/dev/null 2>&1 || true echo "✅ launchd auto-start installed" echo "Plist: $PLIST_PATH"