-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathsetup
More file actions
executable file
·477 lines (430 loc) · 17.2 KB
/
Copy pathsetup
File metadata and controls
executable file
·477 lines (430 loc) · 17.2 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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
# LinkedOut — Check prerequisites and install AI skills.
#
# Checks system dependencies (Python, PostgreSQL, pgvector), then
# symlinks generated skills into your AI host's skill directory so
# that /linkedout-setup and other LinkedOut skills are available.
#
# Usage:
# ./setup # check prerequisites, install skills
# ./setup --auto # auto-install missing prerequisites
# ./setup --check # verify skills are installed (exit 0/1)
#
# After running this script, open Claude Code in this repo and invoke
# /linkedout-setup to complete database and data setup.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")" && pwd -P)"
SKILLS_OUTPUT="$REPO_ROOT/skills/claude-code"
# ── Host configs ──────────────────────────────────────────────────
# host_name:detect_dir:install_path:routing_filename
HOSTS=(
"Claude Code:$HOME/.claude:$HOME/.claude/skills:CLAUDE.md"
"Codex:$HOME/.agents:$HOME/.agents/skills:AGENTS.md"
"Copilot:$HOME/.github:$HOME/.github/skills:COPILOT.md"
)
# ── Colours (CI-safe) ────────────────────────────────────────────
if [ -t 1 ]; then
GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m'
else
GREEN=''; YELLOW=''; RED=''; BOLD=''; NC=''
fi
# ── Helpers ───────────────────────────────────────────────────────
create_symlink() {
local target="$1" link="$2"
if [ -L "$link" ]; then
local existing
existing="$(readlink -f "$link" 2>/dev/null || true)"
local resolved_target
resolved_target="$(readlink -f "$target" 2>/dev/null || true)"
if [ "$existing" = "$resolved_target" ]; then
return 1 # already correct
fi
rm "$link"
elif [ -e "$link" ]; then
echo -e " ${YELLOW}SKIP${NC} $link (exists, not a symlink)" >&2
return 2
fi
mkdir -p "$(dirname "$link")"
ln -s "$target" "$link"
return 0
}
install_for_host() {
local host_name="$1" detect_dir="$2" install_path="$3" routing_file="$4"
local linked=0 already=0
if [ ! -d "$detect_dir" ]; then
return 0
fi
echo -e " ${GREEN}Found${NC} $host_name ($detect_dir)"
# Symlink each skill directory
for skill_dir in "$SKILLS_OUTPUT"/*/; do
[ -d "$skill_dir" ] || continue
local skill_name
skill_name="$(basename "$skill_dir")"
if create_symlink "$skill_dir" "$install_path/$skill_name"; then
linked=$((linked + 1))
else
already=$((already + 1))
fi
done
# Symlink the routing file (one level up from install_path)
local routing_source="$SKILLS_OUTPUT/$routing_file"
if [ -f "$routing_source" ]; then
local routing_link
routing_link="$(dirname "$install_path")/$routing_file"
if create_symlink "$routing_source" "$routing_link"; then
linked=$((linked + 1))
else
already=$((already + 1))
fi
fi
local parts=""
[ $linked -gt 0 ] && parts="$linked linked"
[ $already -gt 0 ] && parts="${parts:+$parts, }$already already linked"
echo " ${parts:-nothing to link}"
}
# ── Parse flags ──────────────────────────────────────────────────
AUTO_INSTALL=false
for arg in "$@"; do
case "$arg" in
--auto) AUTO_INSTALL=true ;;
--check) ;; # handled below
esac
done
# ── Check mode ────────────────────────────────────────────────────
if [ "${1:-}" = "--check" ]; then
ok=true
for host_entry in "${HOSTS[@]}"; do
IFS=: read -r name detect install routing <<< "$host_entry"
[ -d "$detect" ] || continue
for skill_dir in "$SKILLS_OUTPUT"/*/; do
[ -d "$skill_dir" ] || continue
skill="$(basename "$skill_dir")"
if [ ! -L "$install/$skill" ]; then
echo "Missing: $install/$skill"
ok=false
fi
done
done
$ok && echo "All skills installed." && exit 0
echo "Run ./setup to install." && exit 1
fi
# ── Prerequisite checks ──────────────────────────────────────────
detect_os() {
case "$(uname -s)" in
Darwin*) echo "macos" ;;
Linux*) echo "linux" ;;
*) echo "unknown" ;;
esac
}
check_prerequisites() {
local os="$1"
local missing=()
local install_cmds=()
echo "Checking prerequisites..."
echo ""
# ── Python 3.11+ ─────────────────────────────────────────────
if command -v python3 &>/dev/null; then
local py_version
py_version="$(python3 --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1)"
local py_major py_minor
py_major="${py_version%%.*}"
py_minor="${py_version##*.}"
if [ "$py_major" -ge 3 ] && [ "$py_minor" -ge 11 ] 2>/dev/null; then
echo -e " ${GREEN}✓${NC} Python $(python3 --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')"
else
echo -e " ${RED}✗${NC} Python $py_version (need 3.11+)"
missing+=("Python 3.11+")
if [ "$os" = "macos" ]; then
install_cmds+=("brew install python@3.12")
else
install_cmds+=("sudo apt-get install -y python3 python3-pip python3-venv")
fi
fi
else
echo -e " ${RED}✗${NC} Python not found"
missing+=("Python 3.11+")
if [ "$os" = "macos" ]; then
install_cmds+=("brew install python@3.12")
else
install_cmds+=("sudo apt-get install -y python3 python3-pip python3-venv")
fi
fi
# ── uv (fast Python package manager) ─────────────────────────
if command -v uv &>/dev/null; then
echo -e " ${GREEN}✓${NC} uv $(uv --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
else
echo -e " ${RED}✗${NC} uv not found"
missing+=("uv")
install_cmds+=("curl -LsSf https://astral.sh/uv/install.sh | sh")
fi
# ── PostgreSQL 18+ ──────────────────────────────────────────
local need_pg=false
if command -v psql &>/dev/null; then
local pg_version
pg_version="$(psql --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1)"
local pg_major="${pg_version%%.*}"
if [ "$pg_major" -ge 18 ] 2>/dev/null; then
echo -e " ${GREEN}✓${NC} PostgreSQL $pg_version"
else
echo -e " ${RED}✗${NC} PostgreSQL $pg_version (need 18+)"
need_pg=true
fi
else
echo -e " ${RED}✗${NC} PostgreSQL not found"
need_pg=true
fi
if $need_pg; then
missing+=("PostgreSQL 18+")
if [ "$os" = "macos" ]; then
install_cmds+=("brew install postgresql@18")
else
# Need PGDG apt repo for pg 18
install_cmds+=("pgdg_repo")
install_cmds+=("sudo apt-get install -y postgresql-18 postgresql-18-pgvector")
fi
fi
# ── pgvector extension ───────────────────────────────────────
if ! $need_pg; then
local has_pgvector=false
if [ "$os" = "macos" ]; then
if brew list pgvector &>/dev/null 2>&1; then
has_pgvector=true
fi
else
if dpkg -l 2>/dev/null | grep -q "pgvector\|postgresql.*-pgvector"; then
has_pgvector=true
fi
fi
if $has_pgvector; then
echo -e " ${GREEN}✓${NC} pgvector extension"
elif command -v psql &>/dev/null; then
echo -e " ${YELLOW}?${NC} pgvector — could not confirm installation"
echo " (will be verified when /linkedout-setup creates the database)"
else
echo -e " ${RED}✗${NC} pgvector extension (requires PostgreSQL first)"
missing+=("pgvector")
if [ "$os" = "macos" ]; then
install_cmds+=("brew install pgvector")
else
local pg_maj=""
pg_maj="$(psql --version 2>&1 | grep -oE '[0-9]+' | head -1)"
install_cmds+=("sudo apt-get install -y postgresql-${pg_maj:-18}-pgvector")
fi
fi
else
echo -e " ${RED}✗${NC} pgvector extension (requires PostgreSQL first)"
fi
# ── Report missing deps ─────────────────────────────────────
if [ ${#missing[@]} -gt 0 ]; then
echo ""
echo -e "${YELLOW}Missing prerequisites:${NC} ${missing[*]}"
# Check if PGDG repo is needed
local needs_pgdg=false
for cmd in "${install_cmds[@]}"; do
[[ "$cmd" == "pgdg_repo" ]] && needs_pgdg=true
done
# Build one-shot install command
local one_shot=""
for cmd in "${install_cmds[@]}"; do
if [[ "$cmd" == curl* ]]; then
one_shot="${one_shot}${cmd}; "
fi
done
if [ "$os" = "linux" ]; then
if $needs_pgdg; then
one_shot="${one_shot}sudo install -d /usr/share/postgresql-common/pgdg && sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc && echo \"deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt \$(. /etc/os-release && echo \$VERSION_CODENAME)-pgdg main\" | sudo tee /etc/apt/sources.list.d/pgdg.list > /dev/null; "
fi
local apt_pkgs_one=""
for cmd in "${install_cmds[@]}"; do
[[ "$cmd" == curl* || "$cmd" == "pgdg_repo" ]] && continue
apt_pkgs_one="$apt_pkgs_one ${cmd#sudo apt-get install -y }"
done
[ -n "$apt_pkgs_one" ] && one_shot="${one_shot}sudo apt-get update -qq && sudo apt-get install -y$apt_pkgs_one; sudo service postgresql start 2>/dev/null; "
elif [ "$os" = "macos" ]; then
local brew_pkgs_one=""
for cmd in "${install_cmds[@]}"; do
[[ "$cmd" == curl* ]] && continue
brew_pkgs_one="$brew_pkgs_one ${cmd#brew install }"
done
[ -n "$brew_pkgs_one" ] && one_shot="${one_shot}brew install$brew_pkgs_one; brew services start postgresql 2>/dev/null; "
fi
# ── Auto-install mode: run the commands directly ──────────
if $AUTO_INSTALL; then
echo ""
echo -e "${BOLD}Auto-installing prerequisites...${NC}"
echo ""
one_shot="${one_shot}source \$HOME/.local/bin/env 2>/dev/null || true"
if eval "$one_shot"; then
echo ""
echo -e "${GREEN}Prerequisites installed.${NC} Continuing setup..."
echo ""
return 0
else
echo ""
echo -e "${RED}Auto-install failed.${NC} Install manually and re-run ./setup."
return 1
fi
fi
# ── Manual mode: print instructions ───────────────────────
echo ""
echo -e "${BOLD}Install with:${NC}"
echo ""
# Print standalone installers (curl-based) first
for cmd in "${install_cmds[@]}"; do
if [[ "$cmd" == curl* ]]; then
echo " $cmd"
fi
done
# Print PGDG repo setup if needed
if $needs_pgdg && [ "$os" = "linux" ]; then
echo " # Add PostgreSQL 18 apt repository"
echo " sudo install -d /usr/share/postgresql-common/pgdg"
echo " sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc"
echo " echo \"deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt \$(. /etc/os-release && echo \$VERSION_CODENAME)-pgdg main\" | sudo tee /etc/apt/sources.list.d/pgdg.list"
fi
# Print package manager commands
if [ "$os" = "linux" ]; then
local apt_pkgs=""
for cmd in "${install_cmds[@]}"; do
[[ "$cmd" == curl* || "$cmd" == "pgdg_repo" ]] && continue
apt_pkgs="$apt_pkgs ${cmd#sudo apt-get install -y }"
done
[ -n "$apt_pkgs" ] && echo " sudo apt-get update && sudo apt-get install -y$apt_pkgs"
elif [ "$os" = "macos" ]; then
if ! command -v brew &>/dev/null; then
echo " # Install Homebrew first: https://brew.sh"
echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
echo ""
fi
local brew_pkgs=""
for cmd in "${install_cmds[@]}"; do
[[ "$cmd" == curl* ]] && continue
brew_pkgs="$brew_pkgs ${cmd#brew install }"
done
[ -n "$brew_pkgs" ] && echo " brew install$brew_pkgs"
if [[ "$brew_pkgs" == *"postgresql"* ]]; then
echo ""
echo " # Start PostgreSQL after install:"
echo " brew services start postgresql@18"
fi
fi
echo ""
echo "Then re-run ./setup, or run it all in one shot:"
echo ""
one_shot="${one_shot}source \$HOME/.local/bin/env 2>/dev/null; ./setup"
echo " $one_shot"
echo ""
return 1
fi
echo ""
return 0
}
# ── Main ──────────────────────────────────────────────────────────
if [ ! -d "$SKILLS_OUTPUT" ]; then
echo -e "${RED}Error:${NC} $SKILLS_OUTPUT not found."
echo "Run bin/generate-skills first, or check your clone."
exit 1
fi
echo ""
echo "LinkedOut — Setup"
echo ""
# ── Step 1: Install skills (always, so /linkedout-setup works) ──
echo "Installing skills..."
echo ""
detected=0
for host_entry in "${HOSTS[@]}"; do
IFS=: read -r name detect install routing <<< "$host_entry"
if [ -d "$detect" ]; then
detected=$((detected + 1))
install_for_host "$name" "$detect" "$install" "$routing"
fi
done
if [ $detected -eq 0 ]; then
echo " No AI coding assistants detected."
echo " Install Claude Code, Codex, or Copilot, then re-run ./setup."
echo ""
echo " Checked for:"
for host_entry in "${HOSTS[@]}"; do
IFS=: read -r name detect _ _ <<< "$host_entry"
echo " $name — $detect"
done
exit 0
fi
echo ""
# ── Step 2: Check prerequisites (informational) ─────────────────
OS_TYPE="$(detect_os)"
if ! check_prerequisites "$OS_TYPE"; then
echo "Once installed, open Claude Code and invoke /linkedout-setup."
echo ""
exit 0
fi
# ── Step 3: Initialize PostgreSQL database ─────────────────────
echo "Initializing database..."
echo ""
# Start PostgreSQL if not running
if ! pg_isready -q 2>/dev/null; then
if [ "$OS_TYPE" = "macos" ]; then
brew services start postgresql@18 2>/dev/null || brew services start postgresql 2>/dev/null
else
sudo service postgresql start 2>/dev/null || sudo systemctl start postgresql 2>/dev/null
fi
sleep 2
fi
if pg_isready -q 2>/dev/null; then
echo -e " ${GREEN}✓${NC} PostgreSQL running"
else
echo -e " ${RED}✗${NC} Could not start PostgreSQL"
echo " Start it manually and re-run ./setup"
exit 1
fi
# Create role (idempotent — ignore "already exists" errors)
if sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='linkedout'" 2>/dev/null | grep -q 1; then
echo -e " ${GREEN}✓${NC} Role 'linkedout' exists"
else
if sudo -u postgres psql -c "CREATE ROLE linkedout WITH LOGIN CREATEDB PASSWORD 'linkedout';" 2>/dev/null; then
echo -e " ${GREEN}✓${NC} Created role 'linkedout'"
else
echo -e " ${RED}✗${NC} Could not create role 'linkedout'"
echo " Create it manually: sudo -u postgres psql -c \"CREATE ROLE linkedout WITH LOGIN CREATEDB PASSWORD 'linkedout';\""
exit 1
fi
fi
# Create database (idempotent)
if sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='linkedout'" 2>/dev/null | grep -q 1; then
echo -e " ${GREEN}✓${NC} Database 'linkedout' exists"
else
if sudo -u postgres createdb -O linkedout linkedout 2>/dev/null; then
echo -e " ${GREEN}✓${NC} Created database 'linkedout'"
else
echo -e " ${RED}✗${NC} Could not create database 'linkedout'"
exit 1
fi
fi
# Create pgvector extension (idempotent)
# Install in template1 so all future databases (including linkedout_demo) inherit it
if sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS vector;" 2>/dev/null; then
echo -e " ${GREEN}✓${NC} pgvector extension enabled in template1 (inherited by future databases)"
else
echo -e " ${YELLOW}?${NC} Could not enable pgvector in template1 — will retry during /linkedout-setup"
fi
# Also install in linkedout DB (may have been created before the template change)
if sudo -u postgres psql -d linkedout -tc "SELECT 1 FROM pg_extension WHERE extname='vector'" 2>/dev/null | grep -q 1; then
echo -e " ${GREEN}✓${NC} pgvector extension enabled in linkedout"
else
if sudo -u postgres psql -d linkedout -c "CREATE EXTENSION IF NOT EXISTS vector;" 2>/dev/null; then
echo -e " ${GREEN}✓${NC} Enabled pgvector extension in linkedout"
else
echo -e " ${YELLOW}?${NC} Could not enable pgvector in linkedout — will retry during /linkedout-setup"
fi
fi
echo ""
echo -e "${GREEN}Done.${NC} Next steps:"
echo ""
echo " 1. Open Claude Code in this repo: claude"
echo " 2. Invoke: /linkedout-setup"
echo " 3. Setup handles dependencies, migrations, and data import."
echo ""
echo "After git pull, skills auto-update via symlinks."
echo "Re-run ./setup only if new skills are added."
echo ""