CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | #!/usr/bin/env bash
# =============================================================================
# Gluecron one-command install.
#
# curl -sSL https://gluecron.com/install | bash
#
# What it does (every step is idempotent + verbose):
# 1. Verify git, curl, jq are on PATH
# 2. Resolve Gluecron host (default https://gluecron.com, env override)
# 3. Locate Claude Desktop config (macOS / Linux / WSL)
# 4. Sign in (env vars or interactive prompt) and capture session cookie
# 5. Mint a fresh PAT via POST /api/v2/auth/install-token
# 6. Merge a 'gluecron' MCP server entry into claude_desktop_config.json
# 7. If we're inside a GitHub-origin repo, offer to import it
# 8. Print a "you're done" success banner
#
# No third-party tools beyond plain curl, jq, git. Idempotent. Safe to re-run.
# =============================================================================
set -euo pipefail
# ── pretty printers (match scripts/bootstrap-hetzner.sh style) ───────────────
say() { printf "\n\033[1;34m> %s\033[0m\n" "$*"; }
ok() { printf " \033[32mv\033[0m %s\n" "$*"; }
warn() { printf " \033[33m!\033[0m %s\n" "$*"; }
fail() { printf " \033[31mx\033[0m %s\n" "$*"; exit 1; }
# ── 1. Prerequisites ────────────────────────────────────────────────────────
say "[1/7] Checking prerequisites"
MISSING=()
for tool in git curl jq; do
if ! command -v "$tool" >/dev/null 2>&1; then
MISSING+=("$tool")
fi
done
if [ "${#MISSING[@]}" -gt 0 ]; then
warn "Missing tools: ${MISSING[*]}"
echo " Install them and re-run, e.g.:"
for tool in "${MISSING[@]}"; do
case "$tool" in
jq) echo " macOS: brew install jq | Debian/Ubuntu: sudo apt-get install -y jq" ;;
git) echo " macOS: xcode-select --install | Debian/Ubuntu: sudo apt-get install -y git" ;;
curl) echo " macOS: (preinstalled) | Debian/Ubuntu: sudo apt-get install -y curl" ;;
esac
done
fail "Please install the missing tools above and re-run."
fi
ok "git, curl, jq present"
# ── 2. Host ─────────────────────────────────────────────────────────────────
say "[2/7] Resolving Gluecron host"
HOST="${GLUECRON_HOST:-https://gluecron.com}"
HOST="${HOST%/}" # strip trailing slash
ok "Using host: $HOST"
# ── 3. Locate Claude Desktop config ────────────────────────────────────────
say "[3/7] Locating Claude Desktop config"
UNAME_S="$(uname -s 2>/dev/null || echo unknown)"
MAC_CONF="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
LINUX_CONF="$HOME/.config/Claude/claude_desktop_config.json"
CLAUDE_CONF=""
case "$UNAME_S" in
Darwin*)
CLAUDE_CONF="$MAC_CONF"
;;
Linux*)
# WSL detection
if grep -qi microsoft /proc/version 2>/dev/null; then
if [ -d "$HOME/.config/Claude" ]; then
CLAUDE_CONF="$LINUX_CONF"
else
CLAUDE_CONF="$LINUX_CONF"
warn "WSL detected but no existing Claude config — using $CLAUDE_CONF"
fi
else
CLAUDE_CONF="$LINUX_CONF"
fi
;;
*)
warn "Unknown platform '$UNAME_S' — defaulting to macOS path."
CLAUDE_CONF="$MAC_CONF"
;;
esac
mkdir -p "$(dirname "$CLAUDE_CONF")"
if [ ! -f "$CLAUDE_CONF" ]; then
echo '{}' > "$CLAUDE_CONF"
ok "Created empty $CLAUDE_CONF"
else
ok "Found existing $CLAUDE_CONF"
fi
# ── 4. Sign in ──────────────────────────────────────────────────────────────
say "[4/7] Signing in to $HOST"
USERNAME="${GLUECRON_USERNAME:-}"
PASSWORD="${GLUECRON_PASSWORD:-}"
if [ -z "$USERNAME" ]; then
if [ ! -t 0 ]; then
fail "GLUECRON_USERNAME is unset and stdin is not a TTY. Re-run as: GLUECRON_USERNAME=you GLUECRON_PASSWORD=*** curl -sSL $HOST/install | bash"
fi
printf " Gluecron username: "
read -r USERNAME
fi
if [ -z "$PASSWORD" ]; then
if [ ! -t 0 ]; then
fail "GLUECRON_PASSWORD is unset and stdin is not a TTY."
fi
printf " Gluecron password: "
stty -echo
read -r PASSWORD
stty echo
printf "\n"
fi
COOKIE_JAR="$(mktemp -t gluecron-cookies.XXXXXX)"
trap 'rm -f "$COOKIE_JAR"' EXIT
LOGIN_BODY="username=$(printf %s "$USERNAME" | jq -sRr @uri)&password=$(printf %s "$PASSWORD" | jq -sRr @uri)"
LOGIN_STATUS=$(
curl -sS -o /dev/null -w "%{http_code}" \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-c "$COOKIE_JAR" \
--data "$LOGIN_BODY" \
"$HOST/login" || echo 000
)
if ! grep -q "session" "$COOKIE_JAR" 2>/dev/null; then
fail "Login failed (HTTP $LOGIN_STATUS). Check username/password."
fi
ok "Signed in as $USERNAME"
# ── 5. Mint PAT ─────────────────────────────────────────────────────────────
say "[5/7] Minting a fresh PAT"
SHORT_TS=$(date +%s | tail -c 7)
MINT_BODY=$(jq -nc --arg name "gluecron-install-$SHORT_TS" --arg scope "admin" \
'{name: $name, scope: $scope}')
MINT_RESPONSE=$(
curl -sS \
-X POST \
-H "Content-Type: application/json" \
-b "$COOKIE_JAR" \
--data "$MINT_BODY" \
"$HOST/api/v2/auth/install-token"
)
PAT=$(printf %s "$MINT_RESPONSE" | jq -r '.token // empty')
if [ -z "$PAT" ]; then
ERR=$(printf %s "$MINT_RESPONSE" | jq -r '.error // .hint // .')
fail "PAT mint failed: $ERR"
fi
PAT_PREFIX=$(printf %s "$PAT" | cut -c1-12)
ok "Created PAT $PAT_PREFIX..."
# ── 6. Merge MCP server entry into Claude Desktop config ───────────────────
say "[6/7] Wiring Claude Desktop -> Gluecron MCP"
TMP_CONF="$(mktemp -t gluecron-conf.XXXXXX)"
jq --arg url "$HOST/mcp" --arg auth "Bearer $PAT" '
. as $root
| (.mcpServers // {}) as $servers
| $root
| .mcpServers = ($servers + {
"gluecron": {
"transport": "http",
"url": $url,
"headers": { "Authorization": $auth }
}
})
' "$CLAUDE_CONF" > "$TMP_CONF"
mv "$TMP_CONF" "$CLAUDE_CONF"
ok "Updated $CLAUDE_CONF"
# ── 7. Optional repo import ─────────────────────────────────────────────────
say "[7/7] Repo import (optional)"
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
ORIGIN_URL=$(git config --get remote.origin.url || true)
if [ -n "$ORIGIN_URL" ] && printf %s "$ORIGIN_URL" | grep -q "github.com"; then
if [ -t 0 ]; then
printf " Import this repo (%s) to Gluecron? [y/N] " "$ORIGIN_URL"
read -r ANSWER
else
ANSWER="${GLUECRON_IMPORT:-n}"
fi
case "$ANSWER" in
y|Y|yes|YES)
IMPORT_BODY="repo_url=$(printf %s "$ORIGIN_URL" | jq -sRr @uri)"
IMPORT_STATUS=$(
curl -sS -o /dev/null -w "%{http_code}" \
-X POST \
-H "Authorization: Bearer $PAT" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "$IMPORT_BODY" \
"$HOST/import/github/repo" || echo 000
)
case "$IMPORT_STATUS" in
2*|3*) ok "Import dispatched (HTTP $IMPORT_STATUS)" ;;
*) warn "Import returned HTTP $IMPORT_STATUS — you can re-run from $HOST/import" ;;
esac
;;
*)
ok "Skipped import"
;;
esac
else
ok "No GitHub origin detected — skipping import"
fi
else
ok "Not inside a git repo — skipping import"
fi
# ── Done ────────────────────────────────────────────────────────────────────
printf "\n"
printf "\033[1;32m============================================================\033[0m\n"
printf "\033[1;32m GLUECRON INSTALL COMPLETE\033[0m\n"
printf "\033[1;32m============================================================\033[0m\n"
printf " Host: %s\n" "$HOST"
printf " PAT prefix: %s...\n" "$PAT_PREFIX"
printf " MCP config: %s\n" "$CLAUDE_CONF"
printf "\n"
printf " v Done. Restart Claude Desktop and try:\n"
printf " \"Open a PR on this repo with a one-line README fix\"\n"
printf "\n"
|