#!/bin/sh # Flint install — https://flint.nuucognition.com/install.sh # # curl -fsSL https://flint.nuucognition.com/install.sh | bash # # This script installs nothing, writes no file, and uses no sudo. It checks # that this machine runs macOS or Linux and has Claude Code, shows a welcome, # and starts Claude Code with the Flint setup prompt. Claude Code then sets up # Flint with you, and asks before each change to your machine. # # The whole script is in main(), and the last line calls it. So a partial # download runs nothing. set -eu main() { base="${FLINT_SETUP_BASE:-https://flint.nuucognition.com}" base="${base%/}" if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then bold=$(printf '\033[1m') dim=$(printf '\033[2m') accent=$(printf '\033[38;5;209m') reset=$(printf '\033[0m') else bold="" dim="" accent="" reset="" fi case "$(uname -s 2>/dev/null || echo unknown)" in Darwin) os_label="macOS" ;; Linux) os_label="Linux" ;; *) say "Flint supports macOS and Linux. This system is $(uname -s 2>/dev/null || echo unknown)." exit 1 ;; esac setup_prompt="Set up Flint on this machine. Fetch $base/setup.md with curl and follow it. The Flint install script started you on $os_label ($(uname -m 2>/dev/null || echo unknown))." # Under 'curl | bash', stdin is this script. The key press reads /dev/tty, and # the agent gets the terminal device (see terminal_input). With no terminal, # print the manual start. if ! (exec /dev/null; then say "Flint setup needs a terminal, and this run has none." say "Open a terminal, start Claude Code, and paste this prompt:" say "" say " $setup_prompt" exit 1 fi if ! command -v claude >/dev/null 2>&1; then say "" say "${bold}Flint setup uses Claude Code${reset}, and this machine does not have it." say "Install Claude Code, then run the Flint install command again:" say "" say " ${accent}curl -fsSL https://claude.ai/install.sh | bash${reset}" say "" say "${dim}More about Claude Code: https://claude.com/claude-code${reset}" exit 1 fi say "" say " ${accent}${bold}Welcome to Flint.${reset}" say "" say " An agent will take you through the setup." say " It asks you before each change to this computer." say "" say " ${dim}Claude Code first asks whether you trust your home folder." say " Choose \"Yes, I trust this folder\" to continue.${reset}" say "" say " Tip: write ${bold}--auto-approve${reset} in the chat, and the agent sets up" say " everything without asking each time. It still asks before it" say " deletes or replaces anything, or needs your password." say "" printf ' %sPress any key to continue, or Ctrl-C to stop.%s' "$dim" "$reset" wait_for_key say "" say "" # Start in the home folder: the Computer Flint goes there, and the setup # does not depend on the folder where this command ran. cd "$HOME" terminal=$(terminal_input) exec claude \ --allowedTools "Bash(curl -fsSL $base/*)" "Bash(flint --version)" "Bash(flint setup handoff check *)" \ -- "$setup_prompt" <"$terminal" } # The terminal input for the agent. Claude Code is a Bun program, and on # macOS a descriptor that is opened through /dev/tty fails in kqueue # ("EINVAL: invalid argument, kqueue"). So give it the real terminal device # (for example /dev/ttys003), else a copy of the terminal on stderr, and use # /dev/tty only as the last choice. terminal_input() { tty_name=$(ps -o tty= -p $$ 2>/dev/null | tr -d ' ' || true) case "$tty_name" in '' | '?' | '??') ;; *) if [ -c "/dev/$tty_name" ] && [ -r "/dev/$tty_name" ]; then printf '/dev/%s' "$tty_name" return fi ;; esac if [ -t 2 ] && [ -e /dev/fd/2 ]; then printf '/dev/fd/2' return fi printf '/dev/tty' } say() { printf '%s\n' "$1" } # Read one key from the terminal with no Enter. POSIX: no 'read -n'. wait_for_key() { old_stty=$(stty -g /dev/null || true) if [ -n "$old_stty" ]; then trap 'stty "$old_stty" /dev/null; exit 130' INT TERM stty -icanon -echo min 1 time 0 /dev/null || true dd bs=1 count=1 /dev/null 2>&1 || true stty "$old_stty" /dev/null || true trap - INT TERM else # No stty: fall back to Enter. read -r _