Install
One command.
It installs the client, signs you in, and connects this machine to your board. Everything it touches is inside your home directory.
curl -fsSL https://graphlit.co/install.sh | shRun it from anywhere. It asks which folder to turn into a project. Already installed? Run graph setup inside the folder you want.
bash · zsh · sh · dash · fish · Git Bash · no sudo, nothing outside your home directory · read the script first
In order
Four things, then you are working
- 01
Installs the client
Into your home directory, in an isolated environment. No sudo, no administrator prompt, and your system Python is never touched.
- 02
Opens your browser to sign in
A code appears in the terminal and the browser opens to confirm it. Create the account there if you do not have one. The terminal is already waiting.
- 03
Asks which folder to link
You are prompted for a path, not the directory you happened to paste this into. That folder becomes a project: an existing codebase is scanned into a graph, an empty one is where the next build lands.
- 04
Connects the board to this machine
From then on, pressing Execute on the canvas runs here, as you, with your permissions, in that folder.
Not only the terminal
Where else it runs
The same graph, the same checks, reached from wherever you already work.
VS Code
Ask usThe graph beside the code it describes — click a box, land in the file that implements it.
- Graph panel in the editor
- Node to file, one click
- Drift and health in Problems
- Re-check on save
- Run a task in a terminal
Built and installable, not yet listed in any marketplace — so today it arrives as a file we send you. Ask us.
Your coding assistant
Ask usThirteen tools over the Model Context Protocol, so the assistant you already pay for reads the drawing and does the work.
- Interpret, plan, build
- Import a repo
- Check drift
- No extra key
- Calls no model itself
Runs against the engine on your own machine today, not as a connector you can add from a directory yet. Ask us.
The limits, stated up front
What it will never do
Never asks for sudo
Everything lands under your home directory. If it ever asks for a password, it is not us.
Never touches system Python
The client gets its own isolated environment, with its own interpreter.
Never runs unprompted
The agent only executes when you press Execute, and only inside the folder you linked.
Never uploads your source
The scan is local. Only the graph leaves the machine: node names, types, file paths, hashes.
Nothing hidden
The script, in full
This is generated by the same function that serves /install.sh, so it cannot drift from what you would actually run.
install.shshow
#!/bin/sh
# Graphlit: install the terminal client.
#
# curl -fsSL https://graphlit.co/install.sh | sh
#
# Read before you run it. This URL serves plain text and always will:
#
# curl -fsSL https://graphlit.co/install.sh | less
#
# In order, and nothing else:
# 1. work out which build this machine needs, from its OS and CPU
# 2. download the client. ONE compiled binary. No Python, no runtime, no
# package manager, nothing installed that you did not ask for
# 3. put ~/.local/bin on PATH, by appending one line to your shell's rc file
# 4. hand over to `graph setup`, which signs you in and then ASKS which
# folder to turn into a project. Run this from anywhere
#
# No sudo. Nothing is written outside your home directory. Undo the whole thing
# with: rm -f ~/.local/bin/graph ~/.local/bin/graphlit
#
# Flags:
# --no-setup install only; do not sign in or link anything
# --api URL point the client at a different deployment
set -eu
API="https://graphlit.co"
APP="https://graphlit.co/app"
RUN_SETUP=1
while [ $# -gt 0 ]; do
case "$1" in
--no-setup) RUN_SETUP=0 ;;
--api) API="$2"; shift ;;
--api=*) API="${1#--api=}" ;;
# Printed inline rather than read back out of the file. Piped from curl,
# "$0" is "sh" and the script has no path on disk to re-read, so a
# self-quoting help is silence exactly where someone asked a question.
-h|--help)
printf '%s\n' \
"Graphlit installer" \
"" \
" curl -fsSL https://graphlit.co/install.sh | sh" \
" curl -fsSL https://graphlit.co/install.sh | sh -s -- --no-setup" \
"" \
" --no-setup install only; do not sign in or link anything" \
" --api URL point the client at a different deployment" \
"" \
"Read the whole script: curl -fsSL https://graphlit.co/install.sh | less"
exit 0 ;;
*) printf 'unknown option: %s\n' "$1" >&2; exit 2 ;;
esac
shift
done
# Colour only when a human is watching. Piped into a log or a CI transcript,
# escape codes are noise that makes the failure harder to read, not easier.
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
B=$(printf '\033[1m'); D=$(printf '\033[2m'); R=$(printf '\033[0m')
G=$(printf '\033[32m'); Y=$(printf '\033[33m')
else
B=''; D=''; R=''; G=''; Y=''
fi
say() { printf '%s\n' "$*"; }
step() { printf '%s==>%s %s%s%s\n' "$G" "$R" "$B" "$*" "$R"; }
note() { printf '%s %s%s\n' "$D" "$*" "$R"; }
die() { printf '\n%serror:%s %s\n' "$Y" "$R" "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }
# ---------------------------------------------------------------------------
# 0. Is this machine one we can install onto?
# ---------------------------------------------------------------------------
# Checked up front rather than discovered three minutes in. The failure that
# matters is not "unsupported OS" — it is a half-installed tool and a PATH entry
# pointing at nothing.
OS=$(uname -s 2>/dev/null || echo unknown)
case "$OS" in
Linux|Darwin) ;;
MINGW*|MSYS*|CYGWIN*)
note "Git Bash detected. This works, but PowerShell is the better door on Windows:"
note " irm https://graphlit.co/install.ps1 | iex"
;;
*) die "unsupported operating system: $OS. Ask us at https://graphlit.co/contact and we will look." ;;
esac
if [ "$(id -u 2>/dev/null || echo 1)" = "0" ] && [ -z "${ALLOW_ROOT:-}" ]; then
# Not a security theatre check: installed as root, the tool lands in /root/.local
# and is invisible to the account that will actually use it — so the user's
# next command is "command not found" with a successful install behind it.
die "running as root would install into root's home, where your own shell cannot see it.
Run it as yourself (no sudo). If you really mean it: ALLOW_ROOT=1 sh -c '...'"
fi
have curl || have wget || die "needs curl or wget to fetch anything."
say ""
say " ${B}Graphlit${R} · installing the terminal client"
say ""
# ---------------------------------------------------------------------------
# 1. Which build
# ---------------------------------------------------------------------------
# **The client is a compiled binary now, so there is no Python step at all.**
# This section used to install `uv`, which shipped its own Python, which then
# installed a wheel — three moving parts to run one program, and the last of
# them never existed: nothing was ever published as `graphlit-cli`
# (BLOCKERS.md §1). A single 10MB download replaces all of it.
#
# The architecture is asked for explicitly rather than guessed. `uname -m`
# spells the same machine `arm64` on macOS and `aarch64` on Linux, and
# `x86_64` / `amd64` interchangeably — a wrong guess downloads a binary that
# fails with "cannot execute binary file", which reads as a corrupt download
# rather than a wrong build.
MACHINE=$(uname -m 2>/dev/null || echo unknown)
case "$MACHINE" in
x86_64|amd64) ARCH=x86_64 ;;
arm64|aarch64) ARCH=aarch64 ;;
*) die "unsupported CPU: $MACHINE. Tell us at https://graphlit.co/contact and we will add it." ;;
esac
case "$OS" in
Linux) PLATFORM="linux-$ARCH" ;;
Darwin) PLATFORM="darwin-$ARCH" ;;
esac
step "Building for $PLATFORM"
# ---------------------------------------------------------------------------
# 2. The client
# ---------------------------------------------------------------------------
# Downloaded to a temp file and moved into place only once it is complete and
# executable. A `curl` writing straight to the destination leaves a truncated
# binary on $PATH when the network drops, and the next thing the user runs is
# a corrupt `graph` — worse than no install at all, because it looks like the
# product is broken rather than the download.
step "Downloading the graph CLI"
note "from $API/v1/cli/download/$PLATFORM"
BIN_DIR="$HOME/.local/bin"
mkdir -p "$BIN_DIR"
TMP=$(mktemp "${TMPDIR:-/tmp}/graph.XXXXXX") || die "could not create a temporary file"
# shellcheck disable=SC2064
trap "rm -f '$TMP'" EXIT INT TERM
if have curl; then
curl -fsSL "$API/v1/cli/download/$PLATFORM" -o "$TMP" || DOWNLOAD_FAILED=1
else
wget -qO "$TMP" "$API/v1/cli/download/$PLATFORM" || DOWNLOAD_FAILED=1
fi
if [ -n "${DOWNLOAD_FAILED:-}" ] || [ ! -s "$TMP" ]; then
die "could not download the client for $PLATFORM from $API.
If your platform is not published yet, this will say so:
curl -s $API/v1/cli/platforms"
fi
chmod +x "$TMP" || die "could not make the download executable"
# A last check before it goes on $PATH: a binary that cannot answer --version
# is one that will fail on the user's first real command instead, by which time
# they have stopped associating it with the install.
"$TMP" --version >/dev/null 2>&1 || die "the downloaded client did not run. Try again, or tell us at https://graphlit.co/contact."
mv -f "$TMP" "$BIN_DIR/graph" || die "could not install to $BIN_DIR"
trap - EXIT INT TERM
ln -sf "$BIN_DIR/graph" "$BIN_DIR/graphlit" 2>/dev/null || true
note "installed to $BIN_DIR/graph"
# ---------------------------------------------------------------------------
# 3. PATH
# ---------------------------------------------------------------------------
# Delegated to uv rather than appending a line ourselves. uv knows where it put
# the binaries, knows which rc file this shell reads, and knows how fish spells
# it — three things a hand-written export gets wrong on someone's machine and
# nowhere else. It is also idempotent, which a naive >> is not.
step "Putting it on your PATH"
PATH="$BIN_DIR:$PATH"
export PATH
note "$BIN_DIR"
# **Written into the rc file, and this is what `uv tool update-shell` used to
# do for us.** Dropping uv means dropping the one component that knew which rc
# file this shell reads and how fish spells an export, so it is written here —
# guarded, because a naive `>>` appends the same line on every re-run and
# people re-run this when something is wrong.
#
# **Single quotes, and they are load-bearing.** This was written with double
# ones — `LINE="export PATH=\"$BIN_DIR:$PATH\""` — where the inner quotes
# collapse against the outer pair, so `$PATH` expanded *here*, at install time,
# and what landed in the rc file was a snapshot of that moment:
#
# export PATH=/Users/x/.local/bin:/opt/homebrew/bin:/usr/bin:/bin:…
#
# A frozen PATH is not a cosmetic problem. It is re-applied by every future
# shell, so anything computed later by nvm, pyenv or Homebrew is pinned to
# whatever it was on the day this ran, and an entry containing a space is
# unquoted in the bargain. The literal is what belongs in an rc file.
#
# `$HOME/.local/bin` rather than `$BIN_DIR`, for the same reason: the rc file
# is sourced by a shell that has never heard of `$BIN_DIR`, and `export
# PATH=":$PATH"` is what an empty variable would write.
#
# The idempotence guard has to move with it. It matched the *expanded* path,
# which no longer appears in the file — so a re-run would have appended the
# line every time, and people re-run this exactly when something looks wrong.
# Matching `$LINE` also means somebody who already wrote this export by hand
# is left alone.
case "$(basename "${SHELL:-sh}")" in
fish) RC="$HOME/.config/fish/config.fish"; LINE='fish_add_path $HOME/.local/bin' ;;
zsh) RC="$HOME/.zshrc"; LINE='export PATH="$HOME/.local/bin:$PATH"' ;;
bash) RC="$HOME/.bashrc"; LINE='export PATH="$HOME/.local/bin:$PATH"' ;;
*) RC=""; LINE="" ;;
esac
if [ -n "$RC" ] && ! grep -qsF "$LINE" "$RC" 2>/dev/null; then
mkdir -p "$(dirname "$RC")" 2>/dev/null || true
printf '
# added by %s
%s
' "Graphlit" "$LINE" >> "$RC" 2>/dev/null && note "added to $(basename "$RC")" || note "could not write $RC. Add this yourself: $LINE"
elif [ -n "$RC" ]; then
note "already on your PATH in $(basename "$RC")"
fi
have graph || die "installed, but 'graph' is not runnable from $BIN_DIR. Open a new terminal and try 'graph'."
# ---------------------------------------------------------------------------
# 4. Hand over
# ---------------------------------------------------------------------------
# The rest is a tested command in the client itself, not more shell. `graph setup` signs in,
# asks which folder to bind to a project and prints what is connected — and
# because it is a real command, anyone can re-run it later without re-running an
# installer.
#
# stdin is the script, so setup gets /dev/tty when there is one. Without this a
# browser-open prompt would read the remaining bytes of this file as an answer —
# and the folder question below it would be answered by a line of shell.
#
# **Test the open, not the file.** This was `[ -e /dev/tty ] && [ -r /dev/tty ]`,
# which are stat calls: the node exists and its mode bits allow reading. Neither
# says a process can OPEN it, and a process with no controlling terminal — CI, a
# Docker build, `ssh host 'curl … | sh'` — cannot, even though both tests pass.
# The install had already succeeded by then, so the whole run ended
# `cannot open /dev/tty` and a non-zero exit, which reads as "the installer
# failed" when the binary is sitting on $PATH working perfectly. Redirecting in a
# subshell asks the only question that matters.
if [ "$RUN_SETUP" = "1" ]; then
say ""
if (exec </dev/tty) 2>/dev/null; then
graph setup --api "$API" --app "$APP" </dev/tty || exit $?
else
graph setup --api "$API" --app "$APP" --no-browser || exit $?
fi
else
say ""
step "Installed. Skipping setup as asked."
note "When you are ready: graph setup --api $API"
fi
Served verbatim at https://graphlit.co/install.sh
install.ps1show
# Graphlit: the terminal client on Windows.
#
# irm https://graphlit.co/install.ps1 | iex
#
# Read it first if you like. This URL serves plain text:
#
# irm https://graphlit.co/install.ps1
#
# THERE IS NO NATIVE WINDOWS BUILD YET. The client is a single compiled binary
# and it is published for Linux and macOS only. Rather than install something
# that cannot work, this script points you at WSL - which is a real Linux
# machine, runs the ordinary installer unmodified, and is where your project
# almost certainly already lives if you are using one.
#
# If you want to be told when a native build lands: https://graphlit.co/contact
$ErrorActionPreference = 'Stop'
# Windows PowerShell 5.1 negotiates SSLv3/TLS1.0 by default and simply fails
# against any host that has turned those off - which is all of them. Kept even
# though this script no longer downloads anything, because the WSL command it
# prints does, and because the next version of this file will.
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
} catch { }
$Api = if ($env:GRAPHLIT_API) { $env:GRAPHLIT_API } else { 'https://graphlit.co' }
$Unix = "curl -fsSL https://graphlit.co/install.sh | sh"
function Step($m) { Write-Host "==> " -ForegroundColor Green -NoNewline; Write-Host $m -ForegroundColor White }
function Note($m) { Write-Host " $m" -ForegroundColor DarkGray }
Write-Host ""
Write-Host " Graphlit" -ForegroundColor White -NoNewline
Write-Host ": the terminal client on Windows"
Write-Host ""
Step 'There is no native Windows build yet'
Note 'The client ships as one compiled binary, built for Linux and macOS.'
Note 'Nothing has been installed on this machine.'
Write-Host ""
# `Get-Command wsl` is the check, not `wsl --status`: on a machine where WSL has
# never been enabled the `wsl.exe` stub still exists but every subcommand exits
# non-zero with a help screen, so testing the command tells you it is *there*
# and testing a subcommand tells you it *works*. We only need the first, because
# the instruction is the same either way and Microsoft's own installer is one
# line below it.
if (Get-Command wsl -ErrorAction SilentlyContinue) {
Step 'You already have WSL. Run this inside it'
Write-Host ""
Write-Host " wsl -e sh -c '$Unix'" -ForegroundColor White
Write-Host ""
Note 'Or open your Linux shell and paste the installer there.'
} else {
Step 'Install WSL first, then the client inside it'
Write-Host ""
Write-Host " wsl --install" -ForegroundColor White
Write-Host " # reboot, open Ubuntu, then:"
Write-Host " $Unix" -ForegroundColor White
Write-Host ""
Note 'wsl --install needs an administrator PowerShell. Nothing else here does.'
}
Write-Host ""
Note "The client will talk to $Api"
Note "Questions, or want the native build: https://graphlit.co/contact"
Write-Host ""
Served verbatim at https://graphlit.co/install.ps1
Prefer to do it by hand?
Every step the installer takes is a command you can run yourself, in this order.
curl -fsSL https://graphlit.co/v1/cli/platforms curl -fsSL https://graphlit.co/v1/cli/download/darwin-aarch64 -o ~/.local/bin/graph chmod +x ~/.local/bin/graph graph setup --api https://graphlit.co
The first line lists the builds that exist; swap darwin-aarch64 for yours. Architecture is spelled the Linux way on every platform (aarch64, not arm64), and ~/.local/bin has to be on your PATH.