-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·177 lines (169 loc) · 8.36 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·177 lines (169 loc) · 8.36 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
#!/usr/bin/env bash
# =============================================================================
# install.sh — install the Maestro pack (KeyValue AI-SDLC v2) into your project
# =============================================================================
# Run this from the ROOT of your main repo. It:
# 1. installs OUR skills + commands + agents into your AI-IDE config dirs
# (.claude/ and/or .cursor/ — skills, commands, agents)
# 2. copies the engine + workflows + builder UI into your repo
# (runtime files the /maestro skill shells out to)
#
# That's it — no CLI, no config file, no daemons. Everything runs inside your
# interactive session via the /maestro skill; the engine is stdlib-only python3.
#
# Two ways to run — no clone required either way:
# • Piped: curl -fsSL https://raw.githubusercontent.com/KeyValueSoftwareSystems/kv-skills/main/install.sh \
# | bash -s -- claude-code cursor
# • From a checkout: /path/to/kv-skills/install.sh claude-code
#
# Bare words are IDE targets: claude-code (default) and/or cursor.
#
# Stack filter (optional):
# --stack go,react install core SDLC skills/agents + ONLY those tagged stack:go /
# stack:react. Repeatable and comma-separated. `--stack all` (or no
# flag) installs everything. Items with no `stack:` tag are CORE and
# always installed. /maestro-init passes this automatically after
# detecting your repo's stack.
#
# Env overrides:
# KV_SKILLS_REPO pack's GitHub slug (default KeyValueSoftwareSystems/kv-skills)
# KV_SKILLS_REF git ref for the tarball (default main)
# DEST where to copy runtime (default: current directory)
# =============================================================================
set -uo pipefail # not -e: one failed skill install must not abort the rest
REPO="${KV_SKILLS_REPO:-KeyValueSoftwareSystems/kv-skills}"
REF="${KV_SKILLS_REF:-main}"
DEST="${DEST:-$PWD}"
AGENTS=""
STACKS="" # empty => install everything (back-compat)
while [ $# -gt 0 ]; do
case "$1" in
--stack) shift; STACKS="$STACKS ${1:-}" ;;
--stack=*) STACKS="$STACKS ${1#*=}" ;;
-*) echo "unknown flag: $1" >&2; exit 2 ;;
*) AGENTS="$AGENTS $1" ;;
esac
shift
done
AGENTS="${AGENTS# }"
[ -n "$AGENTS" ] || AGENTS="claude-code"
# normalise the stack filter: commas -> spaces, collapse whitespace, `all` clears it
STACKS="$(printf '%s' "$STACKS" | tr ',' ' ')"
STACKS="$(echo $STACKS)"
case " $STACKS " in *" all "*) STACKS="" ;; esac
say() { printf '\n\033[1m%s\033[0m\n' "$*"; }
note() { printf ' %s\n' "$*"; }
# --- stack filtering -------------------------------------------------------
# Stack tokens declared on an item's frontmatter `tags:` line, e.g. "go react".
# Empty output => the item is stack-agnostic CORE and is always installed.
frontmatter_stacks() { # $1 = .md file
grep -m1 '^tags:' "$1" 2>/dev/null \
| grep -oE 'stack:[A-Za-z0-9_+-]+' \
| sed 's/^stack://' \
| tr '\n' ' '
}
# want_item <md-file> — true if this item should be installed under the current filter.
want_item() {
[ -z "$STACKS" ] && return 0 # no filter -> everything
st="$(frontmatter_stacks "$1")"
[ -z "$st" ] && return 0 # core -> always
for s in $st; do
for w in $STACKS; do
[ "$s" = "$w" ] && return 0
done
done
return 1
}
copy_skills() { # $1 = dst dir — filtered by --stack
# SOURCE is organized into category folders (skills/core/…, skills/stacks/<x>/…,
# skills/maestro); the INSTALLED tree is intentionally FLAT — one dir per skill keyed
# by its frontmatter name, which is the only identity workflows/harness discovery use.
# So we find every SKILL.md at any depth and flatten each skill dir into $1/.
mkdir -p "$1"; kept=0; skipped=0
while IFS= read -r skillmd; do
d="$(dirname "$skillmd")"
if want_item "$skillmd"; then cp -R "$d" "$1/" && kept=$((kept+1))
else skipped=$((skipped+1)); fi
done < <(find "$SRC/skills" -name SKILL.md -type f | sort)
note "skills -> $1 ($kept installed, $skipped skipped by --stack)"
}
copy_agents() { # $1 = dst dir — filtered by --stack
mkdir -p "$1"; kept=0; skipped=0
for f in "$SRC"/agents/*.md; do
[ -f "$f" ] || continue
if want_item "$f"; then cp "$f" "$1/" && kept=$((kept+1))
else skipped=$((skipped+1)); fi
done
note "agents -> $1 ($kept installed, $skipped skipped by --stack)"
}
# ---------------------------------------------------------------- source dir
# From a REAL checkout: use the files next to this script. Piped (curl | bash): fetch the
# tarball. The distinction is BASH_SOURCE[0] — it is set only when bash reads the script
# from a file, and unset when the script comes from stdin. Relying on $0 here was the bug:
# piped, $0 is "bash", dirname -> ".", so an already-installed repo looked like a checkout
# and every re-run/upgrade silently copied that repo onto itself instead of fetching.
SRC=""
CLEANUP=""
if [ -n "${BASH_SOURCE[0]:-}" ]; then
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || true)"
if [ -n "$script_dir" ] && [ -f "$script_dir/engine/maestroctl.py" ]; then
SRC="$script_dir"
fi
fi
if [ -z "$SRC" ]; then
say "Fetching $REPO@$REF …"
tmp="$(mktemp -d)"
CLEANUP="$tmp"
if curl -fsSL "https://codeload.github.com/$REPO/tar.gz/refs/heads/$REF" | tar -xz -C "$tmp" 2>/dev/null; then
SRC="$(find "$tmp" -maxdepth 1 -mindepth 1 -type d | head -1)"
fi
[ -n "$SRC" ] && [ -f "$SRC/engine/maestroctl.py" ] || {
echo "could not fetch $REPO@$REF — is the repo private or the ref wrong?" >&2
echo " private repo? clone it and run ./install.sh from the checkout instead." >&2
exit 1
}
fi
trap '[ -n "$CLEANUP" ] && rm -rf "$CLEANUP"' EXIT
# ---------------------------------------------------------------- 1. skills/commands/agents
copy_tree() { # copy_tree <src-subdir> <dst-dir>
mkdir -p "$2"
cp -R "$SRC/$1/." "$2/" && note "$1 -> $2"
}
for agent in $AGENTS; do
case "$agent" in
claude-code)
say "Installing skills + commands + agents for Claude Code${STACKS:+ (stacks: $STACKS)}"
copy_skills "$DEST/.claude/skills"
copy_tree commands "$DEST/.claude/commands"
copy_agents "$DEST/.claude/agents"
;;
cursor)
say "Installing skills + commands for Cursor${STACKS:+ (stacks: $STACKS)}"
copy_skills "$DEST/.cursor/skills"
copy_tree commands "$DEST/.cursor/commands"
# Cursor has no subagent registry; the /maestro skill degrades to inline mode.
;;
*) echo " unknown IDE target: $agent (skipping)" >&2 ;;
esac
done
# ---------------------------------------------------------------- 2. runtime files
# Everything Maestro ships lives under a single .maestro/ parent to keep the consumer
# repo root clean and avoid colliding with the repo's own engine/ docs/ ui/ dirs:
# .maestro/engine .maestro/ui — regenerated runtime (gitignore)
# .maestro/workflows .maestro/docs — committed, customize freely
# .maestro/memory .maestro/runs/<slug>/ — committed (created by the engine at run time)
say "Copying runtime into $DEST/.maestro"
mkdir -p "$DEST/.maestro/workflows" "$DEST/.maestro/engine" "$DEST/.maestro/ui" "$DEST/.maestro/docs"
cp -R "$SRC/engine/." "$DEST/.maestro/engine/" && note ".maestro/engine/ (stdlib-only python3)"
rm -rf "$DEST/.maestro/engine/tests" "$DEST/.maestro/engine/__pycache__" 2>/dev/null
cp -R "$SRC/workflows/." "$DEST/.maestro/workflows/" && note ".maestro/workflows/ (example pack — customize freely)"
cp "$SRC/ui/builder.html" "$DEST/.maestro/ui/builder.html" && note ".maestro/ui/builder.html (visual workflow builder)"
cp "$SRC/docs/workflow-spec.md" "$DEST/.maestro/docs/workflow-spec.md" 2>/dev/null && note ".maestro/docs/workflow-spec.md"
# The repo-local dev wrapper + a copy of this installer, so `./maestro ui` and
# `./maestro install` work without re-fetching. The wrapper never writes run state.
cp "$SRC/bin/maestro" "$DEST/maestro" && chmod +x "$DEST/maestro" && note "maestro (dev wrapper: ui + install)"
cp "$SRC/install.sh" "$DEST/install.sh" 2>/dev/null && note "install.sh (re-run to upgrade)"
say "Done."
note "Next: open your IDE and run /maestro-init (detect stack + build the knowledge base)"
note "Then: start a feature with /maestro my-feature"
note "Guide: ./maestro help (getting started, .gitignore, upgrade/uninstall)"