Repository navigation
Expand file tree
/
Copy pathsetup
More file actions
executable file
·197 lines (188 loc) · 8.53 KB
/
Copy pathsetup
File metadata and controls
executable file
·197 lines (188 loc) · 8.53 KB
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
#!/usr/bin/env bash
# Sets up this clone for developing Dispatch, on Linux or macOS: the Node and Rust it pins, the
# compiler cache its builds share, its packages, the browser `dispatchdev shot` drives, its first
# build and `dispatchdev` on PATH, then the private agent docs, linked into the folder that holds
# this clone, where sessions start. Run it again at any time: it does only what's missing, and
# updates the agent docs.
set -euo pipefail
cd "$(dirname "$0")"
shell_path=$PATH
say() { printf '\033[1m%s\033[0m\n' "$*"; }
fail() {
printf 'setup: %s\n' "$*" >&2
exit 1
}
# Whether version $1 is at least $2, both written x.y.z.
at_least() {
local IFS=.
local have=($1) want=($2) i
for i in 0 1 2; do
((${have[i]:-0} > ${want[i]:-0})) && return 0
((${have[i]:-0} < ${want[i]:-0})) && return 1
done
return 0
}
sha256() { if command -v sha256sum >/dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi; }
# Where it downloads to, removed when it ends.
download=$(mktemp -d)
trap 'rm -rf "$download"' EXIT
# The toolchains. Rust needs a C compiler to link, which takes an administrator to install.
command -v git >/dev/null || fail "Install git first."
command -v python3 >/dev/null || fail "Install Python 3 first."
python3 tooling/ci/python.py || exit 1
if ! command -v cc >/dev/null; then
case $(uname -s) in
Darwin) fail "Install Apple's command line tools first: xcode-select --install" ;;
*) fail "Install your distribution's compiler tools first (build-essential, base-devel)." ;;
esac
fi
# Node: the version .node-version names, or a later one of its major. When the node on PATH
# isn't one, the official build goes in ~/.local/lib/nodejs, linked from ~/.local/bin.
node_version=$(cat .node-version)
usable_node() {
local have
have=$(node --version 2>/dev/null) || return 1
have=${have#v}
[[ ${have%%.*} == "${node_version%%.*}" ]] && at_least "$have" "$node_version"
}
if ! usable_node; then
case $(uname -s)-$(uname -m) in
Linux-x86_64) platform=linux-x64 ;;
Linux-aarch64) platform=linux-arm64 ;;
Darwin-x86_64) platform=darwin-x64 ;;
Darwin-arm64) platform=darwin-arm64 ;;
*) fail "Install Node $node_version first: there's no official build for $(uname -sm)." ;;
esac
name=node-v$node_version-$platform
say "Installing Node $node_version in ~/.local/lib/nodejs/$name"
curl -fsSL "https://nodejs.org/dist/v$node_version/$name.tar.xz" -o "$download/$name.tar.xz"
curl -fsSL "https://nodejs.org/dist/v$node_version/SHASUMS256.txt" -o "$download/SHASUMS256.txt"
(cd "$download" && grep " $name.tar.xz\$" SHASUMS256.txt | sha256 -c - >/dev/null) ||
fail "Node's download doesn't match its published checksum."
mkdir -p "$HOME/.local/lib/nodejs" "$HOME/.local/bin"
tar -xf "$download/$name.tar.xz" -C "$HOME/.local/lib/nodejs"
for tool in node npm npx; do ln -sfn "$HOME/.local/lib/nodejs/$name/bin/$tool" "$HOME/.local/bin/$tool"; done
shell_node=$(command -v node || true)
export PATH=$HOME/.local/bin:$PATH
if [[ $shell_node != "$HOME/.local/bin/node" ]]; then
say "Put ~/.local/bin ahead of ${shell_node%/node} on your PATH, so your shell finds this Node."
fi
fi
# Rust: rustup installs the toolchain rust-toolchain.toml names.
if ! command -v rustup >/dev/null; then
if [[ ! -x $HOME/.cargo/bin/rustup ]]; then
say "Installing rustup"
curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs |
sh -s -- -y --profile minimal --default-toolchain none
fi
export PATH=$HOME/.cargo/bin:$PATH
fi
rustup toolchain install --no-self-update >/dev/null
# sccache: tooling/build/rustc-remap.py compiles through it when it is on PATH, so worktrees and
# test runs share the crates they compile. Without one, its pinned release goes in ~/.local/bin.
if ! command -v sccache >/dev/null; then
sccache_version=0.19.0
case $(uname -s)-$(uname -m) in
Linux-x86_64)
platform=x86_64-unknown-linux-musl
checksum=aa73c7270c997894038c0d8f0d297865f57569e1687883322bc431713d4bb9dd
;;
Linux-aarch64)
platform=aarch64-unknown-linux-musl
checksum=3e47fc4a71b1ddd1b4294a0de9d34498922fdf41e3cd0bda5df3954e3d6deb9d
;;
Darwin-x86_64)
platform=x86_64-apple-darwin
checksum=2db7344bb96de350df79cd9f0124dd4e9baa47b3de2fac1e63e1346b66b20684
;;
Darwin-arm64)
platform=aarch64-apple-darwin
checksum=a35a0b7d622618774bc1bb8102d72562dfb21e619f88b153b370d7f89bc286af
;;
*) fail "Install sccache $sccache_version first: there's no official build for $(uname -sm)." ;;
esac
name=sccache-v$sccache_version-$platform
say "Installing sccache $sccache_version in ~/.local/bin"
curl -fsSL "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/mozilla/sccache/releases/download/v$sccache_version/$name.tar.gz" \
-o "$download/$name.tar.gz"
(cd "$download" && echo "$checksum $name.tar.gz" | sha256 -c - >/dev/null) ||
fail "sccache's download doesn't match its pinned checksum."
tar -xzf "$download/$name.tar.gz" -C "$download"
mkdir -p "$HOME/.local/bin"
install -m 755 "$download/$name/sccache" "$HOME/.local/bin/sccache"
export PATH=$HOME/.local/bin:$PATH
[[ $(PATH=$shell_path command -v sccache) == "$HOME/.local/bin/sccache" ]] ||
say "Put ~/.local/bin on your PATH, so builds find sccache."
fi
# The packages, the browser `dispatchdev shot` drives, and the first build: the CLI, then the
# backend `npm run dev` serves.
if [[ ! -f node_modules/.package-lock.json || package-lock.json -nt node_modules/.package-lock.json ]]; then
say "Installing packages"
npm ci --no-audit --no-fund
fi
npx playwright install chromium
tooling/cli/dispatchdev build
# The loopback addresses the API tests connect from, which a Mac adds only with sudo.
tooling/testing/loopback || true
# `dispatchdev` on PATH, in ~/.local/bin: it runs the dispatchdev of the checkout the current
# directory is in, so a worktree runs its own, or this clone's outside one. Setup replaces only
# a link or a script of its own there.
clone=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
command=$HOME/.local/bin/dispatchdev
marker="Installed by dispatch-platform's ./setup"
if [[ -L $command || ! -e $command ]] || grep -qF "$marker" "$command"; then
mkdir -p "$HOME/.local/bin"
rm -f "$command"
cat > "$command" <<SCRIPT
#!/usr/bin/env bash
# $marker: runs the dispatchdev of the checkout the current directory is in, or of $clone.
top=\$(git rev-parse --show-toplevel 2>/dev/null)
[[ -x \$top/tooling/cli/dispatchdev ]] || top=$(printf %q "$clone")
exec "\$top/tooling/cli/dispatchdev" "\$@"
SCRIPT
chmod 755 "$command"
[[ $(PATH=$shell_path command -v dispatchdev) == "$command" ]] ||
say "Put ~/.local/bin on your PATH, so your shell finds dispatchdev."
else
say "Left $command as it is: move it aside and run setup again to put dispatchdev on PATH."
fi
# The agent docs: the private dispatch-agents repo, in $DISPATCH_AGENTS or ~/dispatch-agents,
# linked into the folder that holds this clone, where every Dispatch session starts: Claude reads
# its CLAUDE.md and .claude/skills, Codex its AGENTS.md and .agents/skills. Setup replaces only a
# link or an empty file there, and removes the links into the docs it once made in ~/.claude,
# ~/.codex and ~/.agents, so sessions elsewhere don't get them.
agents=${DISPATCH_AGENTS:-$HOME/dispatch-agents}
sessions=$(dirname "$clone")
link() {
mkdir -p "$(dirname "$2")"
if [[ -L $2 || ! -e $2 || (-f $2 && ! -s $2) ]]; then
ln -sfn "$1" "$2"
else
say "Left $2 as it is: move it aside and run setup again to link the agent docs there."
fi
}
remove_old_links() {
local entry
for entry in "$@"; do
if [[ -L $entry && $(readlink "$entry") == "$agents"/* ]]; then rm "$entry"; fi
done
}
if [[ -d $agents/.git ]]; then
git -C "$agents" pull -q --ff-only || say "Couldn't update $agents; linking it as it is."
elif ! gh repo clone dispatch-systems/dispatch-agents "$agents" -- -q 2>/dev/null &&
! git clone -q https://github.com/dispatch-systems/dispatch-agents.git "$agents"; then
say "Couldn't clone dispatch-systems/dispatch-agents: the agent docs aren't linked."
fi
if [[ -d $agents/skills ]]; then
link "$agents/CLAUDE.md" "$sessions/CLAUDE.md"
link "$agents/AGENTS.md" "$sessions/AGENTS.md"
for skill in "$agents"/skills/*/; do
skill=${skill%/}
link "$skill" "$sessions/.claude/skills/${skill##*/}"
link "$skill" "$sessions/.agents/skills/${skill##*/}"
done
remove_old_links "$HOME/.claude/CLAUDE.md" "$HOME/.codex/AGENTS.md" "$HOME"/.claude/skills/* \
"$HOME"/.agents/skills/*
fi
command -v gh >/dev/null || say "Install GitHub's gh to open PRs from here."
say "Ready: npm run dev serves the dashboard on demo data."