-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup
More file actions
executable file
·556 lines (498 loc) · 21.1 KB
/
setup
File metadata and controls
executable file
·556 lines (498 loc) · 21.1 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
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
#!/usr/bin/env bash
# Perry setup — install perry/ + link child skills (okr, pmo, design)
# as siblings so the host (Claude Code at ~/.claude/skills/, Codex CLI at
# ~/.agents/skills/) surfaces /perry, /okr, /pmo, /design.
#
# Both hosts read SKILL.md frontmatter natively for skill discovery; the
# install is just a symlink in the host's canonical skills directory.
#
# Host selection:
# No flag → auto-detect; install for whichever host(s) are present
# in PATH (claude / codex). Fails if neither is detected
# and no flag forces.
# --claude → force install for Claude Code (~/.claude/skills/)
# --codex → force install for Codex CLI (~/.agents/skills/)
# --claude --codex → install for both regardless of detection
#
# Before the symlink phase, runs a dependency check (Phase 0). On a fresh
# macOS, several deps may be missing — git (Xcode CLT), Homebrew, gtimeout
# (coreutils), Node.js (for codex), codex CLI. The script reports each, and
# with --yes-deps will auto-install the ones that can be installed
# non-interactively.
set -e
SETUP_DIR="$(cd "$(dirname "$0")" && pwd)"
SOURCE_DIR="$(cd "$(dirname "$0")" && pwd -P)" # resolves symlinks
# ─── Parse flags ──────────────────────────────────────────────
LOCAL=0
QUIET=0
EXPLICIT_CLAUDE=0
EXPLICIT_CODEX=0
ANY_EXPLICIT=0
DEPS_MODE="prompt" # prompt | yes | no | check-only
while [ $# -gt 0 ]; do
case "$1" in
--local) LOCAL=1; shift ;;
--claude) EXPLICIT_CLAUDE=1; ANY_EXPLICIT=1; shift ;;
--codex) EXPLICIT_CODEX=1; ANY_EXPLICIT=1; shift ;;
--quiet) QUIET=1; shift ;;
--yes-deps) DEPS_MODE="yes"; shift ;;
--no-deps) DEPS_MODE="no"; shift ;;
--check-deps-only) DEPS_MODE="check-only"; shift ;;
-h|--help)
cat <<'USAGE'
Perry setup — register the Perry skill set with your host (Claude Code or Codex CLI or both).
Usage: setup [--claude] [--codex] [--local] [--yes-deps|--no-deps|--check-deps-only] [--quiet]
Host selection:
(no flag) Auto-detect: install for whichever of claude / codex is in
PATH. Both → install both. Neither → fail with instructions.
--claude Force install for Claude Code (~/.claude/skills/).
--codex Force install for Codex CLI (~/.agents/skills/).
Combine --claude --codex to force both regardless of detection.
--local Per-project Claude Code install (./.claude/skills/).
Has no effect on Codex install — Codex install is always global.
Dependency handling (Phase 0, runs before symlink phase):
(default) Check deps; prompt for each missing one.
--yes-deps Auto-install everything that can be installed
non-interactively (Homebrew, coreutils, Node, codex).
Items needing GUI / sudo / external download still
require user action and are flagged.
--no-deps Skip the dep check entirely; just do the symlink phase.
--check-deps-only Report dep status and exit; do not install or symlink.
--quiet Suppress non-error output.
What setup does:
Phase 0 — dependency check (skippable with --no-deps):
git, curl, bash, perl — always required (pre-installed on Mac;
Xcode CLT prompts for git on first run)
claude — required only if installing for Claude Code
codex — required only if installing for Codex CLI
(also useful as an executor regardless of host)
Homebrew — optional gate for other Mac installs
coreutils (gtimeout) — soft; Perry's codex preflight uses timeout
if available, else runs unprotected (warning)
Node.js + npm — required for `npm install -g @openai/codex`
Phase 1 — symlink (per selected host):
For each target skills directory:
1. Symlinks perry/ → this script's source.
2. Symlinks each child skill (okr, pmo, design) as a sibling pointing
at perry/<name>.
Re-running is safe and idempotent.
USAGE
exit 0 ;;
*) echo "Unknown flag: $1 (try --help)" >&2; exit 1 ;;
esac
done
log() { [ "$QUIET" -eq 1 ] || echo "$@"; }
warn() { echo "$@" >&2; }
fail() { echo "$@" >&2; exit 1; }
# ─── OS detection ─────────────────────────────────────────────
OS="$(uname -s)"
IS_MAC=0; IS_LINUX=0
case "$OS" in
Darwin*) IS_MAC=1 ;;
Linux*) IS_LINUX=1 ;;
esac
dep_present() { command -v "$1" >/dev/null 2>&1; }
# ─── TTY / agent-context detection ────────────────────────────
# When invoked by Claude Code / Codex CLI / any agent's Bash tool, stdin
# is typically NOT a TTY. Interactive Y/N prompts (`read -r`) would hang
# or return EOF, leaving the user with no idea what was skipped. Detect
# this and switch DEPS_MODE from "prompt" to a non-blocking variant
# ("auto-skip") that still runs the dep CHECK but never tries to install
# or read from stdin.
IS_INTERACTIVE=1
if [ ! -t 0 ]; then IS_INTERACTIVE=0; fi
if [ "$IS_INTERACTIVE" -eq 0 ] && [ "$DEPS_MODE" = "prompt" ]; then
DEPS_MODE="auto-skip" # internal: check + report, skip all installs
fi
# Tracks items skipped in auto-skip mode so we can surface a clear "next
# steps" summary at the end of Phase 0 (the agent can relay these to the
# user as TODOs).
DEPS_SKIPPED_INSTALL=()
# ─── Host selection: which hosts are we installing for? ───────
INSTALL_CLAUDE=0
INSTALL_CODEX=0
if [ "$ANY_EXPLICIT" -eq 1 ]; then
# User specified at least one of --claude / --codex — honor their choice
INSTALL_CLAUDE=$EXPLICIT_CLAUDE
INSTALL_CODEX=$EXPLICIT_CODEX
else
# Auto-detect from PATH
dep_present claude && INSTALL_CLAUDE=1 || true
dep_present codex && INSTALL_CODEX=1 || true
fi
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ]; then
warn ""
warn "❌ No host detected and no --claude/--codex flag given."
warn ""
warn "Perry installs as a skill for either Claude Code or Codex CLI (or both)."
warn "Detection looks for the 'claude' or 'codex' binaries in PATH."
warn ""
warn "Options:"
warn " 1. Install Claude Code from https://claude.com/download and re-run setup."
warn " 2. Install Codex CLI: npm install -g @openai/codex (needs Node.js)"
warn " 3. Run with explicit flag if a host is installed but not in PATH:"
warn " ~/proj/Perry/setup --claude"
warn " ~/proj/Perry/setup --codex"
warn " ~/proj/Perry/setup --claude --codex"
exit 1
fi
# ─── Phase 0: dependency check ────────────────────────────────
DEPS_MISSING_REQUIRED=()
DEPS_MISSING_SOFT=()
DEPS_MISSING_OPTIONAL=()
confirm() {
case "$DEPS_MODE" in
yes) return 0 ;;
no|check-only|auto-skip) return 1 ;;
esac
# Interactive prompt (TTY only — auto-skip handles non-TTY already)
local prompt="$1"
local reply
printf "%s [Y/n] " "$prompt"
read -r reply || return 1
case "$reply" in
y|Y|yes|YES|"") return 0 ;;
*) return 1 ;;
esac
}
check_deps() {
log ""
log "── Phase 0: dependency check ─────────────────────────────"
log " Installing for: $(
[ "$INSTALL_CLAUDE" -eq 1 ] && printf "claude-code "
[ "$INSTALL_CODEX" -eq 1 ] && printf "codex-cli"
)"
if [ "$IS_INTERACTIVE" -eq 0 ]; then
log " ℹ️ Non-interactive stdin (likely run by an agent's Bash tool)."
log " Mode: $DEPS_MODE — will check + report, but skip any install that would prompt for input or sudo."
log " To install missing deps automatically, re-run with --yes-deps (note: Homebrew install still needs sudo)."
fi
# --- Always required ---
for c in bash curl perl; do
if dep_present "$c"; then
log " ✅ $c ($(command -v "$c"))"
else
log " ❌ $c (REQUIRED, missing — unexpected on macOS / common Linux)"
DEPS_MISSING_REQUIRED+=("$c")
fi
done
# --- git ---
if dep_present git && git --version >/dev/null 2>&1; then
log " ✅ git ($(command -v git))"
else
if [ "$IS_MAC" -eq 1 ]; then
log " ❌ git (REQUIRED. macOS: install Xcode Command Line Tools)"
log " → run: xcode-select --install (GUI prompt, ~5–10 min)"
else
log " ❌ git (REQUIRED. install via your package manager)"
fi
DEPS_MISSING_REQUIRED+=("git")
fi
# --- claude (only required if installing Claude host) ---
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
if dep_present claude; then
log " ✅ claude ($(command -v claude))"
else
log " ❌ claude (REQUIRED for Claude Code install target)"
log " → install from https://claude.com/download"
log " (Or drop --claude / pass --codex only if you don't use Claude Code.)"
DEPS_MISSING_REQUIRED+=("claude")
fi
else
if dep_present claude; then
log " ℹ️ claude (in PATH, but install target is Codex-only — skipping Claude install)"
fi
fi
# --- codex (required if installing Codex host; otherwise optional executor) ---
if [ "$INSTALL_CODEX" -eq 1 ]; then
if dep_present codex; then
log " ✅ codex ($(command -v codex) — $(codex --version 2>/dev/null | head -1))"
else
log " ❌ codex (REQUIRED for Codex CLI install target)"
log " → install: npm install -g @openai/codex (needs Node.js)"
DEPS_MISSING_REQUIRED+=("codex")
fi
else
if dep_present codex; then
log " ✅ codex ($(command -v codex) — $(codex --version 2>/dev/null | head -1)) — available as executor"
else
log " ℹ️ codex (optional — only if you use codex executor in /pmo dispatch)"
DEPS_MISSING_OPTIONAL+=("codex")
fi
fi
# --- Homebrew (Mac only) ---
if [ "$IS_MAC" -eq 1 ]; then
if dep_present brew; then
log " ✅ brew ($(command -v brew))"
else
log " ⚠️ brew (Homebrew not installed — gate for coreutils / node)"
DEPS_MISSING_OPTIONAL+=("brew")
fi
fi
# --- timeout / gtimeout (soft) ---
if dep_present timeout || dep_present gtimeout; then
local which_timeout
which_timeout="$(command -v timeout 2>/dev/null || command -v gtimeout 2>/dev/null)"
log " ✅ timeout ($which_timeout)"
else
log " ⚠️ timeout (soft — perry-codex-preflight degrades gracefully without it)"
log " (Mac: brew install coreutils — installs gtimeout)"
DEPS_MISSING_SOFT+=("coreutils")
fi
# --- Node.js (optional, for codex install path) ---
if dep_present node; then
log " ✅ node ($(command -v node) — $(node --version 2>/dev/null))"
else
log " ℹ️ node (optional — needed only for codex install / executor)"
DEPS_MISSING_OPTIONAL+=("node")
fi
log ""
# --- Decision summary ---
if [ ${#DEPS_MISSING_REQUIRED[@]} -gt 0 ]; then
log "Required missing: ${DEPS_MISSING_REQUIRED[*]}"
fi
if [ ${#DEPS_MISSING_SOFT[@]} -gt 0 ]; then
log "Soft missing : ${DEPS_MISSING_SOFT[*]} (Perry still works)"
fi
if [ ${#DEPS_MISSING_OPTIONAL[@]} -gt 0 ]; then
log "Optional missing: ${DEPS_MISSING_OPTIONAL[*]}"
fi
if [ "$DEPS_MODE" = "check-only" ]; then
log ""
log "─ --check-deps-only: not installing anything; not symlinking. Exit."
exit 0
fi
}
# ─── Installer helpers ────────────────────────────────────────
install_homebrew() {
[ "$IS_MAC" -eq 0 ] && return 0
dep_present brew && return 0
log ""
log "📦 Homebrew installer (needs sudo password during install):"
log " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
if confirm " Install Homebrew now?"; then
# NONINTERACTIVE=1 skips "press RETURN to continue" but does NOT skip
# sudo. On a fresh Mac, the sudo prompt will still appear (interactive
# only). When this script is invoked by an agent's Bash tool (no TTY),
# confirm() returns 1 in auto-skip mode and we never reach this line.
if [ "$DEPS_MODE" = "yes" ]; then
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
else
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
if [ -x /opt/homebrew/bin/brew ]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [ -x /usr/local/bin/brew ]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
if ! dep_present brew; then
warn " Homebrew installed but not on PATH. Re-open the terminal or re-run setup."
return 1
fi
else
log " Skipped Homebrew install."
DEPS_SKIPPED_INSTALL+=("Homebrew: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"")
return 1
fi
}
install_coreutils() {
dep_present timeout && return 0
dep_present gtimeout && return 0
if ! dep_present brew; then
log " (coreutils needs Homebrew — skipping; you can install later: brew install coreutils)"
DEPS_SKIPPED_INSTALL+=("coreutils (needs Homebrew first): brew install coreutils")
return 0
fi
log ""
log "📦 coreutils (provides gtimeout):"
if confirm " brew install coreutils now?"; then
brew install coreutils
else
DEPS_SKIPPED_INSTALL+=("coreutils: brew install coreutils")
fi
}
install_node() {
dep_present node && return 0
if ! dep_present brew; then
log " (Node.js needs Homebrew or a separate install — skipping)"
DEPS_SKIPPED_INSTALL+=("Node.js (needs Homebrew first): brew install node")
return 0
fi
log ""
log "📦 Node.js (needed only for codex CLI):"
if confirm " brew install node now?"; then
brew install node
else
DEPS_SKIPPED_INSTALL+=("Node.js: brew install node")
fi
}
install_codex_cli() {
dep_present codex && return 0
if ! dep_present npm; then
log " (codex needs npm — skipping; install Node.js first)"
DEPS_SKIPPED_INSTALL+=("codex CLI (needs Node.js first): npm install -g @openai/codex")
return 0
fi
log ""
log "📦 codex CLI (only needed for codex executor in /pmo dispatch):"
if confirm " npm install -g @openai/codex now?"; then
npm install -g @openai/codex
else
DEPS_SKIPPED_INSTALL+=("codex CLI: npm install -g @openai/codex")
fi
}
# ─── Run Phase 0 ──────────────────────────────────────────────
if [ "$DEPS_MODE" != "no" ]; then
check_deps
if [ ${#DEPS_MISSING_REQUIRED[@]} -gt 0 ]; then
warn ""
warn "❌ Required dependencies missing: ${DEPS_MISSING_REQUIRED[*]}"
for d in "${DEPS_MISSING_REQUIRED[@]}"; do
case "$d" in
git)
[ "$IS_MAC" -eq 1 ] \
&& warn " Run: xcode-select --install (then re-run this setup)" \
|| warn " Install git via your distro's package manager."
;;
claude)
warn " Download Claude Code: https://claude.com/download"
warn " (Or drop --claude / pass --codex only if you don't use Claude Code.)"
;;
codex)
warn " Install codex: npm install -g @openai/codex (needs Node.js)"
warn " (Or drop --codex / pass --claude only if you don't use Codex CLI.)"
;;
*)
warn " $d is missing — install before re-running setup."
;;
esac
done
warn ""
warn "Re-run setup after installing, or pass --no-deps to skip the dep check (not recommended)."
exit 1
fi
# Soft + optional installs (Mac auto-installs via brew)
if [ "$IS_MAC" -eq 1 ]; then
install_homebrew || true
install_coreutils || true
# Only offer Node + codex CLI if user is installing Codex host OR explicitly
# told us about codex via --yes-deps. Don't push it on Claude-only users.
if [ "$INSTALL_CODEX" -eq 1 ] || [ "$DEPS_MODE" = "yes" ]; then
install_node || true
install_codex_cli || true
fi
else
for d in "${DEPS_MISSING_SOFT[@]}" "${DEPS_MISSING_OPTIONAL[@]}"; do
log " (Linux: install $d via your package manager if you need it)"
done
fi
# Surface any skipped install actions as a clear "next steps" block.
# The agent calling setup can relay this to the user as TODOs.
if [ ${#DEPS_SKIPPED_INSTALL[@]} -gt 0 ]; then
log ""
log "📋 Skipped installs — run these manually if you want the soft/optional deps:"
for item in "${DEPS_SKIPPED_INSTALL[@]}"; do
log " • $item"
done
if [ "$IS_INTERACTIVE" -eq 0 ]; then
log ""
log " (Setup is in auto-skip mode because stdin isn't a TTY. To auto-install"
log " everything possible, ask the user to re-run setup interactively, or run with --yes-deps.)"
fi
fi
log ""
log "── Phase 0 done ──────────────────────────────────────────"
fi
# ─── Phase 1: install / symlink ───────────────────────────────
install_at() {
local host_dir="$1"
local label="$2"
mkdir -p "$host_dir"
local perry_target="$host_dir/perry"
if [ "$SETUP_DIR" = "$perry_target" ]; then
log " [$label] perry/ already lives at $perry_target (running from there)"
elif [ -L "$perry_target" ]; then
local resolved="$(cd "$perry_target" && pwd -P)"
if [ "$resolved" = "$SOURCE_DIR" ]; then
log " [$label] perry/ symlink already points at $SOURCE_DIR"
else
warn " [$label] perry/ symlink points at $resolved"
warn " [$label] this script lives at $SOURCE_DIR — leaving existing link in place."
warn " [$label] rm \"$perry_target\" and re-run if you want to repoint."
fi
elif [ -d "$perry_target" ]; then
warn " [$label] perry/ exists at $perry_target (not a symlink) — leaving in place."
warn " [$label] Sync new content with: rsync -a --delete \"$SOURCE_DIR/\" \"$perry_target/\""
elif [ -e "$perry_target" ]; then
warn " [$label] $perry_target exists and is not a directory — refusing to touch."
return 1
else
log " [$label] linking perry/ -> $SOURCE_DIR"
ln -snf "$SOURCE_DIR" "$perry_target"
fi
if [ ! -d "$perry_target" ]; then
warn " [$label] perry directory not found at $perry_target — aborting child link"
return 1
fi
local linked=() skipped=()
for child_dir in "$perry_target"/*/; do
[ -f "$child_dir/SKILL.md" ] || continue
local name="$(basename "$child_dir")"
local target="$host_dir/$name"
if [ -L "$target" ]; then
local existing="$(readlink "$target")"
if [ "$existing" = "perry/$name" ]; then
continue
fi
ln -snf "perry/$name" "$target"
linked+=("$name")
elif [ ! -e "$target" ]; then
ln -snf "perry/$name" "$target"
linked+=("$name")
else
skipped+=("$name (real file/dir at $target)")
fi
done
[ ${#linked[@]} -gt 0 ] && log " [$label] linked child skills: ${linked[*]}" || true
[ ${#skipped[@]} -gt 0 ] && warn " [$label] skipped: ${skipped[*]}" || true
return 0
}
log ""
log "── Phase 1: skill symlink ────────────────────────────────"
# Claude Code install
CC_DIR=""
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
if [ "$LOCAL" -eq 1 ]; then
CC_DIR="$(pwd)/.claude/skills"
else
CC_DIR="$HOME/.claude/skills"
fi
install_at "$CC_DIR" "claude-code"
fi
# Codex install
CODEX_DIR=""
if [ "$INSTALL_CODEX" -eq 1 ]; then
if [ "$LOCAL" -eq 1 ]; then
log ""
log " Note: --local applies to Claude Code only; Codex install always goes to global ~/.agents/skills/"
fi
CODEX_DIR="$HOME/.agents/skills"
install_at "$CODEX_DIR" "codex"
if [ -z "${PERRY_HOME:-}" ] || [ -z "${PERRY_HOST:-}" ]; then
log ""
log " [codex] Recommended shell exports (so SKILL.md \$PERRY_HOME refs are unambiguous):"
[ -z "${PERRY_HOME:-}" ] && log " export PERRY_HOME=\"$CODEX_DIR/perry\"" || true
[ -z "${PERRY_HOST:-}" ] && log " export PERRY_HOST=codex-cli" || true
fi
fi
# ─── Summary ──────────────────────────────────────────────────
log ""
log "Perry ready."
log " source : $SOURCE_DIR"
[ "$INSTALL_CLAUDE" -eq 1 ] && log " claude-code : $CC_DIR" || true
[ "$INSTALL_CODEX" -eq 1 ] && log " codex : $CODEX_DIR" || true
log " skills : /perry, /okr, /pmo, /design"
log ""
[ "$INSTALL_CLAUDE" -eq 1 ] && log " Claude Code: invoke /perry inside a project for the combined dashboard." || true
[ "$INSTALL_CODEX" -eq 1 ] && log " Codex CLI : invoke /skills, then pick perry — or type \$perry to mention." || true
exit 0