Skip to content

Commit ea1992f

Browse files
feat: harden installer and improve install reliability (#93)
* feat: add release workflow and enhance install script with checksum verification and local install options * fix: update release workflow to commit checksums to main and adjust trigger conditions * fix: update checksum generation to use find for better file selection * Update checksums.txt * feat: add per-project configuration support and update installation script * Update checksums.txt * fix: use CLAUDE_PEON_DIR for packs directory in tests * Update checksums.txt * feat: add support for CLAUDE_PEON_DIR override in config and state directory resolution * Update checksums.txt * feat: auto-detect repo URL and default to kenyiu/peon-ping fork - Add detect_repo_info() and detect_clone_url() functions to install.sh - Auto-detect current repo from git remote when running from local clone - Support PEON_REPO_URL and PEON_CLONE_URL environment variables - Default to kenyiu/peon-ping instead of tonyyont/peon-ping - Update peon.sh version check URL to use kenyiu fork - Update update message to reference correct install URL * Update checksums.txt * docs: update README to point to kenyiu fork * feat: add local_only config option for local installation only - Add local_only field to config.json (default: false) - Update peon.sh to check global config for local_only setting - When local_only is true and no local config exists, exit silently - Document the option in README.md This allows teams to enforce per-project configuration by setting local_only: true in the global config. The hook will only run when a local ./.claude/hooks/peon-ping/config.json exists. * Update checksums.txt * feat: support explicit global/local install modes Make installer behavior deterministic by separating global and local modes, adding init-local-config, and prompting to resolve conflicting existing installs. * Update checksums.txt * docs: update contributor link for Orc Peon character in README * Update checksums.txt * feat: enhance security checks for pack installation and checksum generation * Update checksums.txt * docs: streamline README install and config sections --------- Co-authored-by: ken <3939605+kenyiu@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent eedd066 commit ea1992f

7 files changed

Lines changed: 339 additions & 34 deletions

File tree

.github/workflows/release.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ["v*"]
7+
workflow_dispatch:
8+
9+
jobs:
10+
checksums-main:
11+
if: github.ref == 'refs/heads/main'
12+
runs-on: macos-latest
13+
permissions:
14+
contents: write
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Generate checksums
21+
run: |
22+
files=(
23+
install.sh
24+
uninstall.sh
25+
peon.sh
26+
relay.sh
27+
completions.bash
28+
completions.fish
29+
VERSION
30+
config.json
31+
adapters/codex.sh
32+
adapters/cursor.sh
33+
adapters/opencode.sh
34+
adapters/opencode/peon-ping.ts
35+
skills/peon-ping-toggle/SKILL.md
36+
skills/peon-ping-config/SKILL.md
37+
docs/peon-icon.png
38+
)
39+
shasum -a 256 "${files[@]}" > checksums.txt
40+
41+
- name: Commit checksums to main
42+
run: |
43+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
44+
git config --local user.name "github-actions[bot]"
45+
git add checksums.txt
46+
git commit -m "Update checksums.txt" || echo "No changes to commit"
47+
git push
48+
49+
checksums-release:
50+
if: startsWith(github.ref, 'refs/tags/v')
51+
runs-on: macos-latest
52+
permissions:
53+
contents: write
54+
steps:
55+
- uses: actions/checkout@v4
56+
with:
57+
fetch-depth: 0
58+
59+
- name: Generate checksums
60+
run: |
61+
files=(
62+
install.sh
63+
uninstall.sh
64+
peon.sh
65+
relay.sh
66+
completions.bash
67+
completions.fish
68+
VERSION
69+
config.json
70+
adapters/codex.sh
71+
adapters/cursor.sh
72+
adapters/opencode.sh
73+
adapters/opencode/peon-ping.ts
74+
skills/peon-ping-toggle/SKILL.md
75+
skills/peon-ping-config/SKILL.md
76+
docs/peon-icon.png
77+
)
78+
shasum -a 256 "${files[@]}" > checksums.txt
79+
80+
- name: Upload checksums to release
81+
uses: softprops/action-gh-release@v1
82+
with:
83+
tag_name: ${{ github.ref_name }}
84+
generate_release_notes: true
85+
files: checksums.txt
86+
env:
87+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ jobs:
1616
run: brew install bats-core
1717

1818
- name: Run tests
19-
run: bats tests/
19+
run: cd tests && bats .

README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,50 @@ AI coding agents don't notify you when they finish or need permission. You tab a
1212

1313
## Install
1414

15+
### Option 1: Homebrew (recommended)
16+
1517
```bash
1618
brew install PeonPing/tap/peon-ping
1719
```
1820

1921
Then run `peon-ping-setup` to register hooks and download sound packs. macOS and Linux.
2022

21-
**Or install via curl** (macOS, Linux, WSL2):
23+
### Option 2: Installer script (macOS, Linux, WSL2)
2224

2325
```bash
2426
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash
2527
```
2628

27-
One command. Takes 10 seconds. Re-run to update (sounds and config preserved). Installs 10 curated English packs by default.
29+
Installs 10 curated English packs by default. Re-run to update while preserving config/state.
30+
31+
Useful installer flags:
32+
33+
- `--all` — install all available packs
34+
- `--packs=peon,glados,...` — install specific packs only
35+
- `--local` — install into `./.claude/` for current project
36+
- `--global` — explicit global install (same as default)
37+
- `--init-local-config` — create `./.claude/hooks/peon-ping/config.json` only
2838

29-
**Install all packs** (every language and franchise):
39+
`--local` does not modify your shell rc files (no global `peon` alias/completion injection).
40+
41+
Examples:
3042

3143
```bash
3244
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --all
45+
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --packs=peon,glados
46+
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --local
3347
```
3448

35-
**Project-local install** — installs into `.claude/` in the current project instead of `~/.claude/`:
49+
If a global install exists and you install local (or vice versa), the installer prompts you to remove the existing one to avoid conflicts.
50+
51+
### Option 3: Clone and inspect first
3652

3753
```bash
38-
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --local
54+
git clone https://github.com/PeonPing/peon-ping.git
55+
cd peon-ping
56+
./install.sh
3957
```
4058

41-
Local installs don't add the `peon` CLI alias or shell completions — use `/peon-ping-toggle` inside Claude Code instead.
42-
4359
## What you'll hear
4460

4561
| Event | CESP Category | Examples |
@@ -84,7 +100,10 @@ Pausing mutes sounds and desktop notifications instantly. Persists across sessio
84100

85101
peon-ping installs a `/peon-ping-toggle` slash command in Claude Code. You can also just ask Claude to change settings for you — e.g. "enable round-robin pack rotation", "set volume to 0.3", or "add glados to my pack rotation". No need to edit config files manually.
86102

87-
The config lives at `$CLAUDE_CONFIG_DIR/hooks/peon-ping/config.json` (default: `~/.claude/hooks/peon-ping/config.json`):
103+
Config location depends on install mode:
104+
105+
- Global install: `$CLAUDE_CONFIG_DIR/hooks/peon-ping/config.json` (default `~/.claude/hooks/peon-ping/config.json`)
106+
- Local install: `./.claude/hooks/peon-ping/config.json`
88107

89108
```json
90109
{

adapters/opencode.sh

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ OPENCODE_PLUGINS_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/plugins"
2525
PEON_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/peon-ping"
2626
PACKS_DIR="$HOME/.openpeon/packs"
2727

28+
is_safe_source_repo() {
29+
[[ "$1" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]]
30+
}
31+
32+
is_safe_source_ref() {
33+
[[ "$1" =~ ^[A-Za-z0-9._/-]+$ ]] && [[ "$1" != *".."* ]] && [[ "$1" != /* ]]
34+
}
35+
36+
is_safe_source_path() {
37+
[[ "$1" =~ ^[A-Za-z0-9._/-]+$ ]] && [[ "$1" != *".."* ]] && [[ "$1" != /* ]]
38+
}
39+
2840
# --- Colors ---
2941
BOLD=$'\033[1m' DIM=$'\033[2m' RED=$'\033[31m' GREEN=$'\033[32m' YELLOW=$'\033[33m' RESET=$'\033[0m'
3042

@@ -118,34 +130,38 @@ mkdir -p "$PACKS_DIR"
118130
if [ ! -d "$PACKS_DIR/$DEFAULT_PACK" ]; then
119131
info "Installing default sound pack '$DEFAULT_PACK' from registry..."
120132

121-
PACK_INFO=$(curl -fsSL "$REGISTRY_URL" 2>/dev/null \
122-
| python3 -c "
133+
REGISTRY_JSON=$(curl -fsSL "$REGISTRY_URL" 2>/dev/null || true)
134+
PACK_INFO=$(PACK_NAME="$DEFAULT_PACK" python3 -c "
123135
import sys, json
124136
reg = json.load(sys.stdin)
125137
for p in reg.get('packs', []):
126-
if p.get('name') == '$DEFAULT_PACK':
138+
if p.get('name') == __import__('os').environ.get('PACK_NAME'):
127139
print(p.get('source_repo', ''))
128140
print(p.get('source_ref', ''))
129141
print(p.get('source_path', ''))
130142
break
131-
" 2>/dev/null || echo "")
143+
" <<< "$REGISTRY_JSON" 2>/dev/null || echo "")
132144

133145
SOURCE_REPO=$(echo "$PACK_INFO" | sed -n '1p')
134146
SOURCE_REF=$(echo "$PACK_INFO" | sed -n '2p')
135147
SOURCE_PATH=$(echo "$PACK_INFO" | sed -n '3p')
136148

137-
if [ -n "$SOURCE_REPO" ] && [ -n "$SOURCE_REF" ]; then
149+
if is_safe_source_repo "$SOURCE_REPO" && is_safe_source_ref "$SOURCE_REF" && is_safe_source_path "$SOURCE_PATH"; then
138150
TMPDIR_PACK=$(mktemp -d)
139151
TARBALL_URL="https://github.com/${SOURCE_REPO}/archive/refs/tags/${SOURCE_REF}.tar.gz"
140152
if curl -fsSL "$TARBALL_URL" -o "$TMPDIR_PACK/pack.tar.gz" 2>/dev/null; then
141-
tar xzf "$TMPDIR_PACK/pack.tar.gz" -C "$TMPDIR_PACK" 2>/dev/null
142-
EXTRACTED=$(find "$TMPDIR_PACK" -maxdepth 1 -type d ! -path "$TMPDIR_PACK" | head -1)
143-
if [ -n "$EXTRACTED" ] && [ -d "$EXTRACTED/${SOURCE_PATH}" ]; then
144-
mkdir -p "$PACKS_DIR/$DEFAULT_PACK"
145-
cp -r "$EXTRACTED/${SOURCE_PATH}/"* "$PACKS_DIR/$DEFAULT_PACK/"
146-
info "Pack '$DEFAULT_PACK' installed to $PACKS_DIR/$DEFAULT_PACK"
153+
if tar tzf "$TMPDIR_PACK/pack.tar.gz" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then
154+
warn "Pack archive contains unsafe paths; skipping extraction."
147155
else
148-
warn "Could not find pack in downloaded archive."
156+
tar xzf "$TMPDIR_PACK/pack.tar.gz" -C "$TMPDIR_PACK" 2>/dev/null
157+
EXTRACTED=$(find "$TMPDIR_PACK" -maxdepth 1 -type d ! -path "$TMPDIR_PACK" | head -1)
158+
if [ -n "$EXTRACTED" ] && [ -d "$EXTRACTED/${SOURCE_PATH}" ]; then
159+
mkdir -p "$PACKS_DIR/$DEFAULT_PACK"
160+
cp -r "$EXTRACTED/${SOURCE_PATH}/"* "$PACKS_DIR/$DEFAULT_PACK/"
161+
info "Pack '$DEFAULT_PACK' installed to $PACKS_DIR/$DEFAULT_PACK"
162+
else
163+
warn "Could not find pack in downloaded archive."
164+
fi
149165
fi
150166
else
151167
warn "Could not download pack from registry. You can install packs manually later."

checksums.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
88dbc20a548f9c739b526222c0fc19dc4ec32dffcf1fc1f2353cef6672ece7aa install.sh
2+
55198274c3b8a420aa14629a4b62f01b1f3f2deb03bbf411d5564666bc66f768 uninstall.sh
3+
c98f2c3db6351594203f026b5da052ca28387657e8ca97ebff0c971c1d2d085a peon.sh
4+
4c5cdad13d724beea56e3eb87f0a10895edffe648840f6b502f78e89e058c4c8 relay.sh
5+
9ad599c0dab5ccfef8be663201500b6cb0affe083f59447d680a54300657dbac completions.bash
6+
ac253c174720410e10b364014d2d25c5f4b93b7c62978b13782d76a26fdad12d completions.fish
7+
cb1d9a30bc4ab8a397f39b7d786bb82aca0b816077eefc35d7c822017d75f6a5 VERSION
8+
21749ccf66c8878cda11cfd2656a7c1aa94c08a9099d9eb7144a5def9907096b config.json
9+
9590f162df32506b09c6e4012f79e3786bcbb0e402a8bdfe87106f8ef8013e87 adapters/codex.sh
10+
d4cb668ec0ce244d4f1f8b6a0bc828630549d6e6dc1f4e1afe58481aada58f56 adapters/cursor.sh
11+
7778a5c455b53256db31fcdc14eb49c6436b8d2e43d6d0338183ac2378bafbad adapters/opencode.sh
12+
1a7d34971bb43ed685cb8afab7cca7950a59d4159117a9e3e7aa454035fb0490 adapters/opencode/peon-ping.ts
13+
264c87d9bc5065c725c015e9a30e3ba17e428ec99659ab41723ec9838f335f92 skills/peon-ping-toggle/SKILL.md
14+
bbb0b6f218d57728f420e7622c09061a6ba60eafb8d32bcb96f4e401a1e39e9e skills/peon-ping-config/SKILL.md
15+
efa546bce67316a9cc06736b834b2ec2ab54673c3812378cc456e154b9cc6b85 docs/peon-icon.png

0 commit comments

Comments
 (0)