@asterxsk/kiln 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +4 -19
- package/agent/extensions/AGENTS.md +68 -68
- package/agent/extensions/ask-user/install.ps1 +4 -0
- package/agent/extensions/ask-user/install.sh +3 -0
- package/agent/extensions/background-terminals/install.ps1 +4 -0
- package/agent/extensions/background-terminals/install.sh +3 -0
- package/agent/extensions/destructive/install.ps1 +4 -0
- package/agent/extensions/destructive/install.sh +3 -0
- package/agent/extensions/file-search/install.ps1 +4 -0
- package/agent/extensions/file-search/install.sh +3 -0
- package/agent/extensions/goal/install.ps1 +4 -0
- package/agent/extensions/goal/install.sh +3 -0
- package/agent/extensions/herdr-agent-state.ts +257 -0
- package/agent/extensions/kiln-update/README.md +25 -0
- package/agent/extensions/kiln-update/index.ts +99 -0
- package/agent/extensions/kiln-update/install.ps1 +27 -0
- package/agent/extensions/kiln-update/install.sh +24 -0
- package/agent/extensions/modelconf/install.ps1 +4 -0
- package/agent/extensions/modelconf/install.sh +3 -0
- package/agent/extensions/shared/install.ps1 +4 -0
- package/agent/extensions/shared/install.sh +3 -0
- package/agent/extensions/skillsconf/install.ps1 +4 -0
- package/agent/extensions/skillsconf/install.sh +3 -0
- package/agent/extensions/statusline/install.ps1 +4 -0
- package/agent/extensions/statusline/install.sh +3 -0
- package/agent/extensions/subagents/install.ps1 +4 -0
- package/agent/extensions/subagents/install.sh +3 -0
- package/agent/extensions/taste/index.ts +443 -0
- package/agent/extensions/taste/install.ps1 +27 -0
- package/agent/extensions/taste/install.sh +24 -0
- package/agent/extensions/todo/install.ps1 +4 -0
- package/agent/extensions/todo/install.sh +3 -0
- package/agent/extensions/trim-context/install.ps1 +4 -0
- package/agent/extensions/trim-context/install.sh +3 -0
- package/agent/version.txt +1 -0
- package/bin/kiln.js +590 -471
- package/package.json +27 -27
- package/agent/install.ps1 +0 -637
- package/agent/install.sh +0 -620
package/agent/install.sh
DELETED
|
@@ -1,620 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# Pi custom-setup installer — macOS / Linux / Git Bash
|
|
3
|
-
# 1) Install/update pi (@earendil-works/pi-coding-agent)
|
|
4
|
-
# 2) Install pi packages (pi-context-usage, @baretread/pi-forge)
|
|
5
|
-
# 3) Install custom config (AGENTS.md, keybindings, extensions …)
|
|
6
|
-
# 4) Install per-extension npm deps via each extension's install.sh
|
|
7
|
-
#
|
|
8
|
-
# TUI inspired by https://pi.dev/install.sh — spinner when TTY, silent otherwise.
|
|
9
|
-
#
|
|
10
|
-
# Usage:
|
|
11
|
-
# curl -fsSL https://raw.githubusercontent.com/asterxsk/kiln/main/agent/install.sh | sh
|
|
12
|
-
# ./install.sh --repo https://github.com/asterxsk/kiln --branch main
|
|
13
|
-
# ./install.sh --target ~/.pi/agent --yes
|
|
14
|
-
# ./install.sh --local # force local checkout, skip clone
|
|
15
|
-
#
|
|
16
|
-
# Safe to re-run. Managed files are force-overwritten; per-user files
|
|
17
|
-
# (settings.json, taste.md, taste/, auth.json, trust.json, sessions/, etc.) are preserved
|
|
18
|
-
# (pass --overwrite-settings to replace settings.json — otherwise you are asked).
|
|
19
|
-
# Already-installed items are skipped; outdated global packages are updated.
|
|
20
|
-
# Only one timestamped backup (<target>.bak.*) is kept; the clone temp dir
|
|
21
|
-
# under ~/.pi/tmp/kiln-* is always removed.
|
|
22
|
-
|
|
23
|
-
set -eu
|
|
24
|
-
|
|
25
|
-
PI_PACKAGE="@earendil-works/pi-coding-agent"
|
|
26
|
-
NODE_MIN="22.19.0"
|
|
27
|
-
# Overridden at publish time; fallback is used only for local dev
|
|
28
|
-
DEFAULT_REPO="https://github.com/asterxsk/kiln"
|
|
29
|
-
# Disable credential prompts — public clone should never ask for GitHub login
|
|
30
|
-
export GIT_TERMINAL_PROMPT=0
|
|
31
|
-
export GCM_INTERACTIVE=never 2>/dev/null || true
|
|
32
|
-
export GIT_ASKPASS=echo 2>/dev/null || true
|
|
33
|
-
REPO_URL="${PI_CONFIG_REPO:-}"
|
|
34
|
-
BRANCH="${PI_CONFIG_BRANCH:-main}"
|
|
35
|
-
TARGET="${PI_AGENT_DIR:-}"
|
|
36
|
-
SKIP_PI=0
|
|
37
|
-
SKIP_PACKAGES=0
|
|
38
|
-
ASSUME_YES=0
|
|
39
|
-
FORCE_LOCAL=0
|
|
40
|
-
SETTINGS_MODE="ask" # ask | overwrite | keep
|
|
41
|
-
CLONED_TMP=""
|
|
42
|
-
START_TS="$(date +%s 2>/dev/null || echo 0)"
|
|
43
|
-
|
|
44
|
-
# ── tmp + log under ~/.pi/tmp ────────────────────────────────────────────
|
|
45
|
-
mkdir -p "$HOME/.pi/tmp" 2>/dev/null || true
|
|
46
|
-
TMP_BASE="$HOME/.pi/tmp"
|
|
47
|
-
if command -v mktemp >/dev/null 2>&1; then
|
|
48
|
-
TMP_LOG="$(mktemp "$TMP_BASE/pi-setup-XXXXXX.log" 2>/dev/null || echo "$TMP_BASE/pi-setup-$$.log")"
|
|
49
|
-
else
|
|
50
|
-
TMP_LOG="$TMP_BASE/pi-setup-$$.log"
|
|
51
|
-
fi
|
|
52
|
-
: > "$TMP_LOG" 2>/dev/null || true
|
|
53
|
-
# shellcheck disable=SC2064
|
|
54
|
-
trap 'rm -f "$TMP_LOG.sp" 2>/dev/null; if [ -n "$CLONED_TMP" ] && [ -d "$CLONED_TMP" ]; then rm -rf "$CLONED_TMP"; fi' EXIT INT TERM
|
|
55
|
-
|
|
56
|
-
# ── args ────────────────────────────────────────────────────────────────────
|
|
57
|
-
while [ $# -gt 0 ]; do
|
|
58
|
-
case "$1" in
|
|
59
|
-
--repo) REPO_URL="$2"; shift 2;;
|
|
60
|
-
--branch) BRANCH="$2"; shift 2;;
|
|
61
|
-
--target) TARGET="$2"; shift 2;;
|
|
62
|
-
--skip-pi) SKIP_PI=1; shift;;
|
|
63
|
-
--skip-packages) SKIP_PACKAGES=1; shift;;
|
|
64
|
-
--local) FORCE_LOCAL=1; shift;;
|
|
65
|
-
--overwrite-settings) SETTINGS_MODE="overwrite"; shift;;
|
|
66
|
-
--keep-settings) SETTINGS_MODE="keep"; shift;;
|
|
67
|
-
--yes|-y) ASSUME_YES=1; shift;;
|
|
68
|
-
--help|-h) echo "Usage: $0 [--repo URL] [--branch BRANCH] [--target DIR] [--local] [--overwrite-settings|--keep-settings] [--yes]"; exit 0;;
|
|
69
|
-
--) shift; break;;
|
|
70
|
-
-*) echo "unknown arg: $1" >&2; exit 1;;
|
|
71
|
-
*) break;;
|
|
72
|
-
esac
|
|
73
|
-
done
|
|
74
|
-
|
|
75
|
-
# ── ANSI / VT ───────────────────────────────────────────────────────────────
|
|
76
|
-
ESC="$(printf '\033')"
|
|
77
|
-
CR="$(printf '\r')"
|
|
78
|
-
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then HAS_ANSI=1; else HAS_ANSI=0; fi
|
|
79
|
-
if [ "$HAS_ANSI" -eq 1 ]; then
|
|
80
|
-
C_RESET="${ESC}[0m"; C_BOLD="${ESC}[1m"; C_DIM="${ESC}[2m"
|
|
81
|
-
C_GREEN="${ESC}[32m"; C_YELLOW="${ESC}[33m"; C_RED="${ESC}[31m"
|
|
82
|
-
C_CYAN="${ESC}[36m"; C_GRAY="${ESC}[90m"
|
|
83
|
-
else
|
|
84
|
-
C_RESET=""; C_BOLD=""; C_DIM=""; C_GREEN=""; C_YELLOW=""; C_RED=""; C_CYAN=""; C_GRAY=""
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
|
-
# Spinner frames (braille). Kept as a real array so the index advances in the
|
|
88
|
-
# PARENT shell — calling get_spinner inside a $(...) subshell discarded the
|
|
89
|
-
# increment and froze the spinner on frame 0.
|
|
90
|
-
SPINNER_FRAMES=(⠋ ⠙ ⠹ ⠸ ⠼ ⠰ ⠠ ⠦ ⠧ ⠇ ⠏)
|
|
91
|
-
SPINNER_N=${#SPINNER_FRAMES[@]}
|
|
92
|
-
SPINNER_IDX=0
|
|
93
|
-
|
|
94
|
-
write_title() {
|
|
95
|
-
if [ "$HAS_ANSI" -eq 1 ]; then
|
|
96
|
-
printf "${ESC}[1m \u25C6 Pi Setup${ESC}[0m\n${ESC}[2m custom agent config \u00B7 pi + forge + extensions${ESC}[0m\n\n"
|
|
97
|
-
else
|
|
98
|
-
printf "\n Pi Setup\n custom agent config · pi + forge + extensions\n\n"
|
|
99
|
-
fi
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
# ── spinner runner ──────────────────────────────────────────────────────────
|
|
103
|
-
# run_with_spinner STEP LABEL CMD...
|
|
104
|
-
# Synchronous when no TTY (curl|sh, CI, Windows Git Bash without VT).
|
|
105
|
-
# When TTY, shows spinner and overwrites line with \r.
|
|
106
|
-
run_with_spinner() {
|
|
107
|
-
STEP="$1"; LABEL="$2"; shift 2
|
|
108
|
-
: > "$TMP_LOG.sp" 2>/dev/null || true
|
|
109
|
-
if [ "$HAS_ANSI" -eq 0 ]; then
|
|
110
|
-
# plain run — no spinner, keep output quiet (append to log)
|
|
111
|
-
set +e
|
|
112
|
-
"$@" >"$TMP_LOG.sp" 2>&1; CODE=$?
|
|
113
|
-
set -e
|
|
114
|
-
cat "$TMP_LOG.sp" >> "$TMP_LOG" 2>/dev/null || true
|
|
115
|
-
return $CODE
|
|
116
|
-
fi
|
|
117
|
-
# TTY: spinner
|
|
118
|
-
SPINNER_IDX=0
|
|
119
|
-
set +e
|
|
120
|
-
"$@" >"$TMP_LOG.sp" 2>&1 &
|
|
121
|
-
PID=$!
|
|
122
|
-
set -e
|
|
123
|
-
while kill -0 "$PID" 2>/dev/null; do
|
|
124
|
-
SPIN="${SPINNER_FRAMES[$SPINNER_IDX]}"
|
|
125
|
-
SPINNER_IDX=$(( (SPINNER_IDX + 1) % SPINNER_N ))
|
|
126
|
-
# \r + Erase-in-Line so each frame overwrites cleanly regardless of length
|
|
127
|
-
printf "${CR}${ESC}[K ${C_YELLOW}%s${C_RESET} ${C_DIM}[%s/4]${C_RESET} %s" "$SPIN" "$STEP" "$LABEL"
|
|
128
|
-
sleep 0.08
|
|
129
|
-
done
|
|
130
|
-
set +e
|
|
131
|
-
wait "$PID" 2>/dev/null; CODE=$?
|
|
132
|
-
set -e
|
|
133
|
-
cat "$TMP_LOG.sp" >> "$TMP_LOG" 2>/dev/null || true
|
|
134
|
-
printf "${CR}${ESC}[K"
|
|
135
|
-
return $CODE
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
# ── helpers ─────────────────────────────────────────────────────────────────
|
|
139
|
-
resolve_target() {
|
|
140
|
-
if [ -n "$TARGET" ]; then printf "%s" "$TARGET"; return; fi
|
|
141
|
-
# Git Bash on Windows: prefer Windows path if cygpath available
|
|
142
|
-
if command -v cygpath >/dev/null 2>&1; then
|
|
143
|
-
printf "%s" "$(cygpath -w "$HOME/.pi/agent" 2>/dev/null || echo "$HOME/.pi/agent")"
|
|
144
|
-
return
|
|
145
|
-
fi
|
|
146
|
-
printf "%s/.pi/agent" "$HOME"
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
resolve_repo() {
|
|
150
|
-
if [ -n "$REPO_URL" ]; then printf "%s" "$REPO_URL"; return; fi
|
|
151
|
-
# Don't probe when piped (curl | sh) — $0 is sh/dash
|
|
152
|
-
case "$0" in sh|dash|*\/sh|*\/dash|-) printf "%s" "$DEFAULT_REPO"; return;; esac
|
|
153
|
-
HERE=""
|
|
154
|
-
# Try to find a git checkout near the script
|
|
155
|
-
_self=""
|
|
156
|
-
case "$0" in /*|*/*) _self="$(cd "$(dirname "$0")" 2>/dev/null && pwd 2>/dev/null || echo "")";; esac
|
|
157
|
-
for r in "$_self" "$(pwd)" "$(pwd)/agent"; do
|
|
158
|
-
[ -z "$r" ] && continue
|
|
159
|
-
if [ -d "$r/.git" ] || [ -d "$r/../.git" ]; then
|
|
160
|
-
U="$(git -C "$r" remote get-url origin 2>/dev/null || true)"
|
|
161
|
-
if [ -n "$U" ]; then printf "%s" "$U"; return; fi
|
|
162
|
-
fi
|
|
163
|
-
done
|
|
164
|
-
printf "%s" "$DEFAULT_REPO"
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
resolve_source_root() {
|
|
168
|
-
REPO="$1"
|
|
169
|
-
# If forced local or script is inside a checkout, use it
|
|
170
|
-
if [ "$FORCE_LOCAL" -eq 1 ]; then
|
|
171
|
-
for r in "$(cd "$(dirname "$0")" 2>/dev/null && pwd 2>/dev/null || echo "")" "$(pwd)" "$(pwd)/agent"; do
|
|
172
|
-
[ -z "$r" ] && continue
|
|
173
|
-
if [ -d "$r/extensions" ] && [ -f "$r/AGENTS.md" ]; then printf "%s" "$r"; return 0; fi
|
|
174
|
-
if [ -d "$r/agent/extensions" ]; then printf "%s" "$r/agent"; return 0; fi
|
|
175
|
-
done
|
|
176
|
-
else
|
|
177
|
-
# When not piped, check for local checkout first to avoid clone
|
|
178
|
-
case "$0" in sh|dash|*\/sh|*\/dash|-) :;; # piped — skip local check
|
|
179
|
-
*)
|
|
180
|
-
for r in "$(cd "$(dirname "$0")" 2>/dev/null && pwd 2>/dev/null || echo "")" "$(pwd)" "$(pwd)/agent"; do
|
|
181
|
-
[ -z "$r" ] && continue
|
|
182
|
-
if [ -d "$r/extensions" ] && [ -f "$r/AGENTS.md" ]; then printf "%s" "$r"; return 0; fi
|
|
183
|
-
if [ -d "$r/agent/extensions" ]; then printf "%s" "$r/agent"; return 0; fi
|
|
184
|
-
done
|
|
185
|
-
;;
|
|
186
|
-
esac
|
|
187
|
-
fi
|
|
188
|
-
# Clone to ~/.pi/tmp — simple git clone, no account required, then copy agent folder.
|
|
189
|
-
# Use a unique subdir per run so concurrent installs never clobber each other.
|
|
190
|
-
mkdir -p "$TMP_BASE" 2>/dev/null || true
|
|
191
|
-
if command -v mktemp >/dev/null 2>&1; then
|
|
192
|
-
TMP="$(mktemp -d "$TMP_BASE/kiln-XXXXXX" 2>/dev/null || echo "$TMP_BASE/kiln-$$")"
|
|
193
|
-
else
|
|
194
|
-
TMP="$TMP_BASE/kiln-$$"
|
|
195
|
-
fi
|
|
196
|
-
mkdir -p "$TMP" 2>/dev/null || true
|
|
197
|
-
CLONED_TMP="$TMP"
|
|
198
|
-
printf "${C_DIM} → cloning %s (branch %s) → %s${C_RESET}\n" "$REPO" "$BRANCH" "$TMP" >&2
|
|
199
|
-
set +e
|
|
200
|
-
run_with_spinner "3" "fetching config" env GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=echo GCM_INTERACTIVE=never git clone --depth 1 --branch "$BRANCH" "$REPO" "$TMP"
|
|
201
|
-
CODE=$?
|
|
202
|
-
set -e
|
|
203
|
-
if [ $CODE -ne 0 ]; then
|
|
204
|
-
echo "git clone failed (exit $CODE) — see $TMP_LOG" >&2
|
|
205
|
-
return 1
|
|
206
|
-
fi
|
|
207
|
-
if [ -d "$TMP/agent" ]; then printf "%s" "$TMP/agent"; else printf "%s" "$TMP"; fi
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
# Portable version compare: returns 0 if HAVE >= MIN
|
|
211
|
-
version_ge() {
|
|
212
|
-
HAVE="$1"; MIN="$2"
|
|
213
|
-
# Use node itself if available (most reliable), else awk
|
|
214
|
-
if command -v node >/dev/null 2>&1; then
|
|
215
|
-
node -e "const [h,m]=process.argv.slice(1).map(v=>v.split('.').map(Number));let ge=true;for(let i=0;i<Math.max(h.length,m.length);i++){const a=h[i]||0,b=m[i]||0;if(a>b)break;if(a<b){ge=false;break}}process.exit(ge?0:1)" "$HAVE" "$MIN" 2>/dev/null && return 0 || return 1
|
|
216
|
-
fi
|
|
217
|
-
# awk fallback
|
|
218
|
-
printf "%s\n%s\n" "$HAVE" "$MIN" | awk -F. '
|
|
219
|
-
NR==1{split($0,h,"."); next}
|
|
220
|
-
{split($0,m,"."); for(i=1;i<=3;i++){a=h[i]+0; b=m[i]+0; if(a>b)exit 0; if(a<b)exit 1} exit 0}
|
|
221
|
-
' 2>/dev/null && return 0 || return 1
|
|
222
|
-
# last resort: string compare
|
|
223
|
-
[ "$HAVE" = "$MIN" ] && return 0
|
|
224
|
-
[ "$(printf "%s\n%s" "$HAVE" "$MIN" | sort 2>/dev/null | head -n1)" = "$MIN" ] && return 0 || return 1
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
# Installed global version of a package ("" when not installed).
|
|
228
|
-
npm_global_ver() {
|
|
229
|
-
npm ls -g "$1" --depth=0 2>/dev/null | grep -F "$1@" | sed -n 's|.*@\([0-9][0-9A-Za-z.+_-]*\).*|\1|p' | head -n1 | tr -d '\r\n '
|
|
230
|
-
}
|
|
231
|
-
# Latest registry version ("" when offline / unknown).
|
|
232
|
-
npm_latest_ver() {
|
|
233
|
-
_v="$(npm view "$1" version 2>/dev/null | head -n1 | tr -d '\r\n ')"
|
|
234
|
-
case "$_v" in [0-9]*) printf "%s" "$_v";; *) printf "";; esac
|
|
235
|
-
}
|
|
236
|
-
# Keep only the newest sibling backup (<target>.bak.*); delete the rest.
|
|
237
|
-
prune_backups() {
|
|
238
|
-
_pb_keep="$(basename "$2")"
|
|
239
|
-
for _pb_d in "$(dirname "$1")"/"$(basename "$1")".bak.*; do
|
|
240
|
-
[ -e "$_pb_d" ] || continue
|
|
241
|
-
[ "$(basename "$_pb_d")" = "$_pb_keep" ] && continue
|
|
242
|
-
rm -rf "$_pb_d" 2>/dev/null || true
|
|
243
|
-
done
|
|
244
|
-
}
|
|
245
|
-
# Remove stale clone dirs (~/.pi/tmp/kiln-*) except the active one.
|
|
246
|
-
sweep_stale_tmp() {
|
|
247
|
-
for _sw_d in "$TMP_BASE"/kiln-*; do
|
|
248
|
-
[ -e "$_sw_d" ] || continue
|
|
249
|
-
[ -d "$_sw_d" ] || continue
|
|
250
|
-
case "$CLONED_TMP" in "$_sw_d") continue;; esac
|
|
251
|
-
rm -rf "$_sw_d" 2>/dev/null || true
|
|
252
|
-
done
|
|
253
|
-
}
|
|
254
|
-
# Returns 0 when the user wants settings.json overwritten, 1 to keep.
|
|
255
|
-
want_overwrite_settings() {
|
|
256
|
-
if [ "$SETTINGS_MODE" = "overwrite" ]; then return 0; fi
|
|
257
|
-
if [ "$SETTINGS_MODE" = "keep" ]; then return 1; fi
|
|
258
|
-
if [ "$ASSUME_YES" -eq 1 ]; then return 1; fi
|
|
259
|
-
if [ ! -t 0 ] && [ ! -r /dev/tty ]; then return 1; fi
|
|
260
|
-
printf " settings.json exists. Overwrite with repo defaults? [y/N] (Recommended for first-time setup) "
|
|
261
|
-
_ans=""; read -r _ans < /dev/tty 2>/dev/null || read -r _ans 2>/dev/null || _ans=""
|
|
262
|
-
case "$_ans" in [yY]|[yY][eE][sS]) return 0;; *) return 1;; esac
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
# ── MAIN ─────────────────────────────────────────────────────────────────────
|
|
266
|
-
write_title
|
|
267
|
-
|
|
268
|
-
# Copy a tree excluding heavy/unwanted dirs (node_modules, .git).
|
|
269
|
-
# Uses rsync when available, else cp + prune.
|
|
270
|
-
# No-op when source and destination are the same path (self-install).
|
|
271
|
-
copy_tree() {
|
|
272
|
-
_src="$1"; _dst="$2"
|
|
273
|
-
if [ "$_src" = "$_dst" ]; then return 0; fi
|
|
274
|
-
# Resolve canonical paths to catch ./ vs absolute same target
|
|
275
|
-
_src_can="$(cd "$_src" 2>/dev/null && pwd 2>/dev/null || echo "$_src")"
|
|
276
|
-
_dst_can="$(cd "$_dst" 2>/dev/null && pwd 2>/dev/null || echo "$_dst")"
|
|
277
|
-
if [ "$_src_can" = "$_dst_can" ]; then return 0; fi
|
|
278
|
-
rm -rf "$_dst" 2>/dev/null || true
|
|
279
|
-
if command -v rsync >/dev/null 2>&1; then
|
|
280
|
-
rsync -a --exclude node_modules --exclude '.git' "$_src/" "$_dst/" 2>/dev/null || \
|
|
281
|
-
{ cp -R "$_src" "$_dst" 2>/dev/null || true; rm -rf "$_dst/node_modules" "$_dst/.git" 2>/dev/null || true; }
|
|
282
|
-
else
|
|
283
|
-
cp -R "$_src" "$_dst" 2>/dev/null || true
|
|
284
|
-
rm -rf "$_dst/node_modules" "$_dst/.git" 2>/dev/null || true
|
|
285
|
-
fi
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
TARGET_DIR="$(resolve_target)"
|
|
289
|
-
REPO_URL="$(resolve_repo)"
|
|
290
|
-
OK=1
|
|
291
|
-
|
|
292
|
-
printf "${C_DIM} target ${C_RESET}%s\n" "$TARGET_DIR"
|
|
293
|
-
printf "${C_DIM} repo ${C_RESET}%s ${C_DIM}(%s)${C_RESET}\n" "$REPO_URL" "$BRANCH"
|
|
294
|
-
printf "${C_DIM} log ${C_RESET}%s\n\n" "$TMP_LOG"
|
|
295
|
-
|
|
296
|
-
# ── preflight ────────────────────────────────────────────────────────────────
|
|
297
|
-
if ! command -v node >/dev/null 2>&1; then
|
|
298
|
-
printf "${C_RED} ✖ Node.js not found. Install Node %s+ from https://nodejs.org${C_RESET}\n" "$NODE_MIN"
|
|
299
|
-
printf " then re-run this installer.\n\n"
|
|
300
|
-
exit 1
|
|
301
|
-
fi
|
|
302
|
-
NODE_VER="$(node --version 2>/dev/null | sed 's/^v//;s/[^0-9.].*//' | cut -d- -f1)"
|
|
303
|
-
if ! version_ge "$NODE_VER" "$NODE_MIN"; then
|
|
304
|
-
printf "${C_YELLOW} ⚠ Node %s < %s — please upgrade to Node %s+${C_RESET}\n" "$NODE_VER" "$NODE_MIN" "$NODE_MIN"
|
|
305
|
-
if [ "$ASSUME_YES" -eq 0 ]; then printf " continue anyway in 3s…\n"; sleep 3; fi
|
|
306
|
-
fi
|
|
307
|
-
if ! command -v npm >/dev/null 2>&1; then
|
|
308
|
-
printf "${C_RED} ✖ npm not found (Node installed but npm missing)${C_RESET}\n"; exit 1
|
|
309
|
-
fi
|
|
310
|
-
if ! command -v git >/dev/null 2>&1; then
|
|
311
|
-
printf "${C_RED} ✖ git not found — required to fetch config${C_RESET}\n"
|
|
312
|
-
printf " install via xcode-select --install or sudo apt install git\n\n"; exit 1
|
|
313
|
-
fi
|
|
314
|
-
NPM_VER="$(npm --version 2>/dev/null | tr -d '\n' | tr -d '\r')"
|
|
315
|
-
GIT_VER="$(git --version 2>/dev/null | tr -d '\n' | tr -d '\r')"
|
|
316
|
-
printf "${C_DIM} node %s · npm %s · %s${C_RESET}\n\n" "$NODE_VER" "$NPM_VER" "$GIT_VER"
|
|
317
|
-
|
|
318
|
-
# ── [1/4] pi ────────────────────────────────────────────────────────────────
|
|
319
|
-
if [ "$SKIP_PI" -eq 1 ]; then
|
|
320
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[1/4]${C_RESET} Install pi ${C_DIM}— skipped${C_RESET}\n"
|
|
321
|
-
elif command -v pi >/dev/null 2>&1; then
|
|
322
|
-
PI_INSTALLED="$(npm_global_ver "$PI_PACKAGE")"
|
|
323
|
-
if [ -z "$PI_INSTALLED" ]; then PI_INSTALLED="$(pi --version 2>/dev/null | tr -d '\n' | tr -d '\r' | sed 's/^v//')"; fi
|
|
324
|
-
PI_LATEST="$(npm_latest_ver "$PI_PACKAGE")"
|
|
325
|
-
if [ -n "$PI_INSTALLED" ] && [ -n "$PI_LATEST" ] && [ "$PI_INSTALLED" = "$PI_LATEST" ]; then
|
|
326
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[1/4]${C_RESET} pi ${C_DIM}— already installed (%s), skipping${C_RESET}\n" "$PI_INSTALLED"
|
|
327
|
-
elif [ -n "$PI_INSTALLED" ] && [ -z "$PI_LATEST" ]; then
|
|
328
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[1/4]${C_RESET} pi ${C_DIM}— already installed (%s), skipping version check (offline)${C_RESET}\n" "$PI_INSTALLED"
|
|
329
|
-
else
|
|
330
|
-
if [ -n "$PI_INSTALLED" ] && [ -n "$PI_LATEST" ]; then LABEL="updating pi $PI_INSTALLED → $PI_LATEST ($PI_PACKAGE)"; else LABEL="installing pi ($PI_PACKAGE)"; fi
|
|
331
|
-
set +e
|
|
332
|
-
run_with_spinner "1" "$LABEL" npm install -g "$PI_PACKAGE@latest" --no-audit --no-fund --min-release-age=0
|
|
333
|
-
CODE=$?
|
|
334
|
-
set -e
|
|
335
|
-
if [ $CODE -eq 0 ]; then
|
|
336
|
-
PI_VER="$(pi --version 2>/dev/null | tr -d '\n' | tr -d '\r' || echo "?")"
|
|
337
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[1/4]${C_RESET} %s ${C_DIM}— %s${C_RESET}\n" "$LABEL" "$PI_VER"
|
|
338
|
-
else
|
|
339
|
-
# Check for EACCES
|
|
340
|
-
if grep -q "EACCES\|permission" "$TMP_LOG" 2>/dev/null; then
|
|
341
|
-
printf " ${C_RED}✖${C_RESET} ${C_DIM}[1/4]${C_RESET} %s ${C_RED}failed (permission)${C_RESET}\n" "$LABEL"
|
|
342
|
-
printf " ${C_DIM}try: sudo npm install -g %s or npm config set prefix ~/.npm-global${C_RESET}\n" "$PI_PACKAGE"
|
|
343
|
-
else
|
|
344
|
-
printf " ${C_RED}✖${C_RESET} ${C_DIM}[1/4]${C_RESET} %s ${C_RED}failed (exit %s)${C_RESET} ${C_DIM}see %s${C_RESET}\n" "$LABEL" "$CODE" "$TMP_LOG"
|
|
345
|
-
fi
|
|
346
|
-
printf " ${C_DIM}see %s${C_RESET}\n" "$TMP_LOG"
|
|
347
|
-
OK=0
|
|
348
|
-
fi
|
|
349
|
-
fi
|
|
350
|
-
else
|
|
351
|
-
if command -v pi >/dev/null 2>&1; then LABEL="updating pi ($PI_PACKAGE)"; else LABEL="installing pi ($PI_PACKAGE)"; fi
|
|
352
|
-
set +e
|
|
353
|
-
run_with_spinner "1" "$LABEL" npm install -g "$PI_PACKAGE@latest" --no-audit --no-fund --min-release-age=0
|
|
354
|
-
CODE=$?
|
|
355
|
-
set -e
|
|
356
|
-
if [ $CODE -eq 0 ]; then
|
|
357
|
-
PI_VER="$(pi --version 2>/dev/null | tr -d '\n' | tr -d '\r' || echo "?")"
|
|
358
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[1/4]${C_RESET} %s ${C_DIM}— %s${C_RESET}\n" "$LABEL" "$PI_VER"
|
|
359
|
-
else
|
|
360
|
-
# Check for EACCES
|
|
361
|
-
if grep -q "EACCES\|permission" "$TMP_LOG" 2>/dev/null; then
|
|
362
|
-
printf " ${C_RED}✖${C_RESET} ${C_DIM}[1/4]${C_RESET} %s ${C_RED}failed (permission)${C_RESET}\n" "$LABEL"
|
|
363
|
-
printf " ${C_DIM}try: sudo npm install -g %s or npm config set prefix ~/.npm-global${C_RESET}\n" "$PI_PACKAGE"
|
|
364
|
-
else
|
|
365
|
-
printf " ${C_RED}✖${C_RESET} ${C_DIM}[1/4]${C_RESET} %s ${C_RED}failed (exit %s)${C_RESET} ${C_DIM}see %s${C_RESET}\n" "$LABEL" "$CODE" "$TMP_LOG"
|
|
366
|
-
fi
|
|
367
|
-
printf " ${C_DIM}see %s${C_RESET}\n" "$TMP_LOG"
|
|
368
|
-
OK=0
|
|
369
|
-
fi
|
|
370
|
-
fi
|
|
371
|
-
|
|
372
|
-
# ── [2/4] pi packages ─────────────────────────────────────────────────────
|
|
373
|
-
if [ "$SKIP_PACKAGES" -eq 1 ]; then
|
|
374
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[2/4]${C_RESET} pi packages ${C_DIM}— skipped${C_RESET}\n"
|
|
375
|
-
else
|
|
376
|
-
NEED=""
|
|
377
|
-
for _pkg in pi-context-usage @baretread/pi-forge; do
|
|
378
|
-
_inst="$(npm_global_ver "$_pkg")"
|
|
379
|
-
_latest="$(npm_latest_ver "$_pkg")"
|
|
380
|
-
if [ -n "$_inst" ] && [ -n "$_latest" ] && [ "$_inst" = "$_latest" ]; then
|
|
381
|
-
printf "${C_DIM} · %s@%s already installed, skipping${C_RESET}\n" "$_pkg" "$_inst"
|
|
382
|
-
elif [ -n "$_inst" ] && [ -z "$_latest" ]; then
|
|
383
|
-
printf "${C_DIM} · %s@%s already installed, skipping version check (offline)${C_RESET}\n" "$_pkg" "$_inst"
|
|
384
|
-
else
|
|
385
|
-
if [ -n "$_inst" ]; then printf "${C_DIM} · %s %s → %s — will update${C_RESET}\n" "$_pkg" "$_inst" "${_latest:-latest}"; fi
|
|
386
|
-
NEED="$NEED $_pkg@latest"
|
|
387
|
-
fi
|
|
388
|
-
done
|
|
389
|
-
if [ -z "$NEED" ]; then
|
|
390
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[2/4]${C_RESET} pi packages ${C_DIM}— already installed, skipping${C_RESET}\n"
|
|
391
|
-
else
|
|
392
|
-
LABEL="installing$NEED"
|
|
393
|
-
set +e
|
|
394
|
-
# shellcheck disable=SC2086
|
|
395
|
-
run_with_spinner "2" "$LABEL" npm install -g $NEED --no-audit --no-fund
|
|
396
|
-
CODE=$?
|
|
397
|
-
set -e
|
|
398
|
-
if [ $CODE -eq 0 ]; then
|
|
399
|
-
# Verify packages are resolvable (handles npm global prefix quirks with mise/nvm)
|
|
400
|
-
if ! npm ls -g pi-context-usage >/dev/null 2>&1 || ! npm ls -g @baretread/pi-forge >/dev/null 2>&1; then
|
|
401
|
-
printf " ${C_YELLOW}⚠${C_RESET} ${C_DIM}[2/4]${C_RESET} pi packages installed but not in global ls — retrying with prefix check${C_RESET}\n"
|
|
402
|
-
fi
|
|
403
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[2/4]${C_RESET} pi packages ${C_DIM}— done${C_RESET}\n"
|
|
404
|
-
else
|
|
405
|
-
printf " ${C_YELLOW}⚠${C_RESET} ${C_DIM}[2/4]${C_RESET} %s ${C_YELLOW}exit %s — continuing (pi will auto-install)${C_RESET}\n" "$LABEL" "$CODE"
|
|
406
|
-
printf " ${C_DIM}see %s${C_RESET}\n" "$TMP_LOG"
|
|
407
|
-
fi
|
|
408
|
-
fi
|
|
409
|
-
fi
|
|
410
|
-
|
|
411
|
-
# ── [3/4] custom config ───────────────────────────────────────────────────
|
|
412
|
-
set +e
|
|
413
|
-
# NOTE: no command substitution here — resolve_source_root records the clone
|
|
414
|
-
# dir in $CLONED_TMP, which would be lost in a $(...) subshell (that is why
|
|
415
|
-
# ~/.pi/tmp was never deleted after install).
|
|
416
|
-
resolve_source_root "$REPO_URL" >"$TMP_LOG.src"
|
|
417
|
-
SRC_CODE=$?
|
|
418
|
-
SOURCE_ROOT="$(cat "$TMP_LOG.src" 2>/dev/null || echo "")"
|
|
419
|
-
rm -f "$TMP_LOG.src" 2>/dev/null || true
|
|
420
|
-
set -e
|
|
421
|
-
if [ $SRC_CODE -ne 0 ] || [ -z "$SOURCE_ROOT" ] || [ ! -d "$SOURCE_ROOT" ]; then
|
|
422
|
-
printf " ${C_RED}✖${C_RESET} ${C_DIM}[3/4]${C_RESET} custom config ${C_RED}failed:${C_RESET} source not found\n"
|
|
423
|
-
printf " ${C_DIM}see %s${C_RESET}\n" "$TMP_LOG"
|
|
424
|
-
OK=0
|
|
425
|
-
else
|
|
426
|
-
printf "${C_DIM} source %s${C_RESET}\n" "$SOURCE_ROOT"
|
|
427
|
-
mkdir -p "$TARGET_DIR" 2>/dev/null || true
|
|
428
|
-
|
|
429
|
-
# backup (managed files only: extensions + top-level config files).
|
|
430
|
-
# Per-user data (settings.json, auth.json, sessions/, taste/, etc.) is never
|
|
431
|
-
# touched by the installer, so it is deliberately NOT backed up.
|
|
432
|
-
BAK=""
|
|
433
|
-
if [ -f "$TARGET_DIR/AGENTS.md" ] || [ -d "$TARGET_DIR/extensions" ]; then
|
|
434
|
-
BAK="$TARGET_DIR.bak.$(date +%Y%m%d-%H%M%S 2>/dev/null || echo $$)"
|
|
435
|
-
printf "${C_DIM} backup %s/extensions → %s${C_RESET}\n" "$TARGET_DIR" "$BAK"
|
|
436
|
-
set +e
|
|
437
|
-
mkdir -p "$BAK" 2>/dev/null || true
|
|
438
|
-
for f in AGENTS.md keybindings.json README.md; do
|
|
439
|
-
[ -f "$TARGET_DIR/$f" ] && cp -f "$TARGET_DIR/$f" "$BAK/$f" 2>/dev/null || true
|
|
440
|
-
done
|
|
441
|
-
if [ -d "$TARGET_DIR/extensions" ]; then
|
|
442
|
-
copy_tree "$TARGET_DIR/extensions" "$BAK/extensions" || printf "${C_YELLOW} ⚠ backup extensions failed${C_RESET}\n"
|
|
443
|
-
fi
|
|
444
|
-
set -e
|
|
445
|
-
prune_backups "$TARGET_DIR" "$BAK" # only one backup at a time
|
|
446
|
-
fi
|
|
447
|
-
|
|
448
|
-
COPIED=0
|
|
449
|
-
set +e
|
|
450
|
-
for f in AGENTS.md keybindings.json README.md; do
|
|
451
|
-
if [ -f "$SOURCE_ROOT/$f" ]; then
|
|
452
|
-
# Self-install: source == target — no copy needed, just count
|
|
453
|
-
if [ "$SOURCE_ROOT/$f" = "$TARGET_DIR/$f" ] || [ "$SOURCE_ROOT" = "$TARGET_DIR" ]; then
|
|
454
|
-
COPIED=$((COPIED+1))
|
|
455
|
-
else
|
|
456
|
-
cp -f "$SOURCE_ROOT/$f" "$TARGET_DIR/$f" 2>/dev/null && COPIED=$((COPIED+1)) || printf "${C_YELLOW} ⚠ copy %s failed${C_RESET}\n" "$f"
|
|
457
|
-
fi
|
|
458
|
-
fi
|
|
459
|
-
done
|
|
460
|
-
# per-user files: seed from repo defaults if missing; otherwise keep,
|
|
461
|
-
# unless --overwrite-settings is passed or the user answers Y.
|
|
462
|
-
SETTINGS_JUST_CREATED=0
|
|
463
|
-
if [ ! -f "$TARGET_DIR/settings.json" ] && [ -f "$SOURCE_ROOT/settings.json" ]; then
|
|
464
|
-
if cp "$SOURCE_ROOT/settings.json" "$TARGET_DIR/settings.json" 2>/dev/null; then
|
|
465
|
-
printf "${C_DIM} created settings.json from repo defaults${C_RESET}\n"
|
|
466
|
-
SETTINGS_JUST_CREATED=1
|
|
467
|
-
fi
|
|
468
|
-
elif [ -f "$TARGET_DIR/settings.json" ]; then
|
|
469
|
-
if want_overwrite_settings && [ -f "$SOURCE_ROOT/settings.json" ]; then
|
|
470
|
-
if [ -n "$BAK" ]; then cp -f "$TARGET_DIR/settings.json" "$BAK/settings.json" 2>/dev/null || true; fi
|
|
471
|
-
if cp -f "$SOURCE_ROOT/settings.json" "$TARGET_DIR/settings.json" 2>/dev/null; then
|
|
472
|
-
printf "${C_DIM} overwrote settings.json from repo defaults${C_RESET}\n"
|
|
473
|
-
SETTINGS_JUST_CREATED=1
|
|
474
|
-
fi
|
|
475
|
-
else
|
|
476
|
-
printf "${C_DIM} kept existing settings.json${C_RESET}\n"
|
|
477
|
-
fi
|
|
478
|
-
fi
|
|
479
|
-
# ensure Pi extensions (forge + context) are registered — only on a freshly
|
|
480
|
-
# seeded settings.json; an existing file is left completely untouched.
|
|
481
|
-
if [ "$SETTINGS_JUST_CREATED" = "1" ] && [ -f "$TARGET_DIR/settings.json" ] && command -v node >/dev/null 2>&1; then
|
|
482
|
-
node -e "const fs=require('fs');const p=process.argv[1];try{let j=JSON.parse(fs.readFileSync(p,'utf8'));let need=['npm:pi-context-usage','npm:@baretread/pi-forge'];j.packages=j.packages||[];let c=false;for(let pkg of need){if(!j.packages.includes(pkg)){j.packages.push(pkg);c=true}}if(c){fs.writeFileSync(p,JSON.stringify(j,null,2)+'\\n');console.log(' patched settings.json packages → forge + context') } }catch(e){}" "$TARGET_DIR/settings.json" 2>/dev/null || true
|
|
483
|
-
fi
|
|
484
|
-
# taste.md / taste/ — user-specific, preserve if exists
|
|
485
|
-
for _preserve in taste.md taste taste.json; do
|
|
486
|
-
if [ -f "$TARGET_DIR/$_preserve" ] || [ -d "$TARGET_DIR/$_preserve" ]; then
|
|
487
|
-
printf "${C_DIM} kept existing %s${C_RESET}\n" "$_preserve"
|
|
488
|
-
elif [ -e "$SOURCE_ROOT/$_preserve" ]; then
|
|
489
|
-
cp -R "$SOURCE_ROOT/$_preserve" "$TARGET_DIR/$_preserve" 2>/dev/null || true
|
|
490
|
-
fi
|
|
491
|
-
done
|
|
492
|
-
# auth.json / trust.json / sessions etc. are never shipped in kiln, so no copy needed; they remain untouched
|
|
493
|
-
|
|
494
|
-
# extensions — full sync (set +e so one bad copy doesn't abort)
|
|
495
|
-
if [ -d "$SOURCE_ROOT/extensions" ]; then
|
|
496
|
-
mkdir -p "$TARGET_DIR/extensions" 2>/dev/null || true
|
|
497
|
-
for d in "$SOURCE_ROOT/extensions"/*; do
|
|
498
|
-
[ -e "$d" ] || continue
|
|
499
|
-
[ -d "$d" ] || continue
|
|
500
|
-
# skip stray files like AGENTS.md inside extensions
|
|
501
|
-
case "$(basename "$d")" in *.md) continue;; esac
|
|
502
|
-
base="$(basename "$d")"
|
|
503
|
-
copy_tree "$d" "$TARGET_DIR/extensions/$base" && COPIED=$((COPIED+1)) || printf "${C_YELLOW} ⚠ copy extensions/%s failed${C_RESET}\n" "$base"
|
|
504
|
-
done
|
|
505
|
-
fi
|
|
506
|
-
|
|
507
|
-
# themes if present — skip recursive .pi artifact and nul device file
|
|
508
|
-
# Skip if source == target (self-install) — themes already in place
|
|
509
|
-
if [ "$SOURCE_ROOT" != "$TARGET_DIR" ] && [ -d "$SOURCE_ROOT/themes" ]; then
|
|
510
|
-
rm -rf "$TARGET_DIR/themes" 2>/dev/null || true
|
|
511
|
-
mkdir -p "$TARGET_DIR/themes" 2>/dev/null || true
|
|
512
|
-
# copy themes but exclude .pi and nul
|
|
513
|
-
for t in "$SOURCE_ROOT/themes"/*; do
|
|
514
|
-
[ -e "$t" ] || continue
|
|
515
|
-
bn="$(basename "$t")"
|
|
516
|
-
case "$bn" in .pi|nul) continue;; esac
|
|
517
|
-
cp -R "$t" "$TARGET_DIR/themes/$bn" 2>/dev/null || printf "${C_YELLOW} ⚠ copy themes/%s failed${C_RESET}\n" "$bn"
|
|
518
|
-
done
|
|
519
|
-
# also copy files at themes root
|
|
520
|
-
for tf in "$SOURCE_ROOT/themes"/.* "$SOURCE_ROOT/themes"/*; do
|
|
521
|
-
[ -f "$tf" ] || continue
|
|
522
|
-
bn="$(basename "$tf")"
|
|
523
|
-
case "$bn" in .|..|.pi|nul) continue;; esac
|
|
524
|
-
cp -f "$tf" "$TARGET_DIR/themes/$bn" 2>/dev/null || true
|
|
525
|
-
done
|
|
526
|
-
fi
|
|
527
|
-
set -e
|
|
528
|
-
|
|
529
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[3/4]${C_RESET} custom config ${C_DIM}— %s items → %s${C_RESET}\n" "$COPIED" "$TARGET_DIR"
|
|
530
|
-
# cleanup cloned temp — per spec: clone into ~/.pi/tmp, copy agent, delete temp
|
|
531
|
-
case "$CLONED_TMP" in
|
|
532
|
-
"$TMP_BASE"/kiln-*) rm -rf "$CLONED_TMP" 2>/dev/null || true; CLONED_TMP="";;
|
|
533
|
-
esac
|
|
534
|
-
sweep_stale_tmp # delete any leftover kiln-* temp dirs
|
|
535
|
-
# repo README → alongside the agent dir (e.g. ~/.pi/README.md)
|
|
536
|
-
REPO_ROOT="$(cd "$SOURCE_ROOT/.." 2>/dev/null && pwd 2>/dev/null || echo "")"
|
|
537
|
-
TARGET_PARENT="$(cd "$(dirname "$TARGET_DIR")" 2>/dev/null && pwd 2>/dev/null || echo "")"
|
|
538
|
-
if [ -n "$REPO_ROOT" ] && [ -f "$REPO_ROOT/README.md" ] && [ -n "$TARGET_PARENT" ] && [ "$REPO_ROOT" != "$TARGET_PARENT" ]; then
|
|
539
|
-
cp -f "$REPO_ROOT/README.md" "$TARGET_PARENT/README.md" 2>/dev/null || true
|
|
540
|
-
fi
|
|
541
|
-
fi
|
|
542
|
-
|
|
543
|
-
# ── [4/4] per-extension deps ──────────────────────────────────────────────
|
|
544
|
-
EXT_ROOT="$TARGET_DIR/extensions"
|
|
545
|
-
if [ ! -d "$EXT_ROOT" ]; then
|
|
546
|
-
printf " ${C_YELLOW}⚠${C_RESET} ${C_DIM}[4/4]${C_RESET} no extensions found at %s\n" "$EXT_ROOT"
|
|
547
|
-
else
|
|
548
|
-
FAILED=""
|
|
549
|
-
INSTALLED=0
|
|
550
|
-
SKIPPED=0
|
|
551
|
-
set +e
|
|
552
|
-
for ext in "$EXT_ROOT"/*; do
|
|
553
|
-
[ -e "$ext" ] || continue
|
|
554
|
-
[ -d "$ext" ] || continue
|
|
555
|
-
case "$(basename "$ext")" in *.md) continue;; esac
|
|
556
|
-
name="$(basename "$ext")"
|
|
557
|
-
label="extensions/$name"
|
|
558
|
-
if [ -d "$ext/node_modules" ]; then
|
|
559
|
-
printf " ${C_DIM} · %s — already installed, skipping${C_RESET}\n" "$label"
|
|
560
|
-
SKIPPED=$((SKIPPED+1))
|
|
561
|
-
continue
|
|
562
|
-
fi
|
|
563
|
-
has_pkg=0; [ -f "$ext/package.json" ] && has_pkg=1
|
|
564
|
-
has_sh=0; [ -f "$ext/install.sh" ] && has_sh=1
|
|
565
|
-
|
|
566
|
-
if [ "$has_pkg" -eq 0 ] && [ "$has_sh" -eq 0 ]; then
|
|
567
|
-
printf " ${C_DIM} · %s — no package.json, skipping${C_RESET}\n" "$label"
|
|
568
|
-
continue
|
|
569
|
-
fi
|
|
570
|
-
|
|
571
|
-
if [ "$has_sh" -eq 1 ]; then
|
|
572
|
-
run_with_spinner "4" "$label" sh "$ext/install.sh"
|
|
573
|
-
CODE=$?
|
|
574
|
-
else
|
|
575
|
-
if [ -f "$ext/package-lock.json" ]; then
|
|
576
|
-
run_with_spinner "4" "$label" npm --prefix "$ext" ci --no-audit --no-fund
|
|
577
|
-
CODE=$?
|
|
578
|
-
else
|
|
579
|
-
run_with_spinner "4" "$label" npm --prefix "$ext" install --no-audit --no-fund
|
|
580
|
-
CODE=$?
|
|
581
|
-
fi
|
|
582
|
-
fi
|
|
583
|
-
if [ $CODE -eq 0 ]; then
|
|
584
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[4/4]${C_RESET} %s\n" "$label"
|
|
585
|
-
INSTALLED=$((INSTALLED+1))
|
|
586
|
-
else
|
|
587
|
-
printf " ${C_RED}✖${C_RESET} ${C_DIM}[4/4]${C_RESET} %s ${C_RED}failed (exit %s)${C_RESET}\n" "$label" "$CODE"
|
|
588
|
-
FAILED="$FAILED $name"
|
|
589
|
-
fi
|
|
590
|
-
done
|
|
591
|
-
set -e
|
|
592
|
-
if [ -z "$FAILED" ]; then
|
|
593
|
-
printf " ${C_GREEN}✔${C_RESET} ${C_DIM}[4/4]${C_RESET} extension deps ${C_DIM}— %s installed, %s skipped${C_RESET}\n" "$INSTALLED" "$SKIPPED"
|
|
594
|
-
else
|
|
595
|
-
# count without wc -w (no fork)
|
|
596
|
-
CNT=0; for _ in $FAILED; do CNT=$((CNT+1)); done
|
|
597
|
-
printf " ${C_YELLOW}⚠${C_RESET} ${C_DIM}[4/4]${C_RESET} extension deps ${C_YELLOW}%s failed:${C_RESET}%s ${C_DIM}see %s${C_RESET}\n" "$CNT" "$FAILED" "$TMP_LOG"
|
|
598
|
-
fi
|
|
599
|
-
fi
|
|
600
|
-
|
|
601
|
-
# ── summary ─────────────────────────────────────────────────────────────────
|
|
602
|
-
END_TS="$(date +%s 2>/dev/null || echo 0)"
|
|
603
|
-
ELAPSED=$((END_TS - START_TS))
|
|
604
|
-
[ "$ELAPSED" -lt 0 ] 2>/dev/null && ELAPSED=0
|
|
605
|
-
if [ "$ELAPSED" -ge 60 ]; then
|
|
606
|
-
ELAPSED_FMT="$((ELAPSED / 60))m $((ELAPSED % 60))s"
|
|
607
|
-
else
|
|
608
|
-
ELAPSED_FMT="${ELAPSED}s"
|
|
609
|
-
fi
|
|
610
|
-
printf "\n"
|
|
611
|
-
if [ "$OK" -eq 1 ]; then
|
|
612
|
-
printf "${C_GREEN}${C_BOLD} ✓ Setup complete${C_RESET} ${C_DIM}→ %s · Kiln installed in %s${C_RESET}\n" "$TARGET_DIR" "$ELAPSED_FMT"
|
|
613
|
-
if command -v pi >/dev/null 2>&1; then
|
|
614
|
-
PI_VER2="$(pi --version 2>/dev/null | tr -d '\n' | tr -d '\r' || echo "")"
|
|
615
|
-
if [ -n "$PI_VER2" ]; then printf "${C_DIM} pi %s · run: pi${C_RESET}\n" "$PI_VER2"; fi
|
|
616
|
-
fi
|
|
617
|
-
else
|
|
618
|
-
printf "${C_YELLOW}${C_BOLD} ⚠ Setup finished with warnings${C_RESET} ${C_DIM}in %s · see %s${C_RESET}\n" "$ELAPSED_FMT" "$TMP_LOG"
|
|
619
|
-
fi
|
|
620
|
-
printf "${C_DIM} log: %s${C_RESET}\n\n" "$TMP_LOG"
|