chore: remove duplicated archy skills and redundant hooks
Wrong archy-specific skills (harden, refactor, test, ux-review, lint, add-app, pwa-icon-cache-fix) removed — these referenced Archipelago infrastructure irrelevant to botfights. Redundant hooks (block-risky-bash, protect-files, post-deploy-check, post-push-progress) removed since global hooks provide superset protection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ebf6667f8f
commit
52752a92bf
@@ -1,76 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# PreToolUse Bash guard: block dangerous shell commands.
|
|
||||||
# Denies: rm -rf, git reset --hard, git push -f, git clean -fd, chmod -R 777,
|
|
||||||
# fork bombs, block device overwrites, mkfs, building Rust on macOS for Linux.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
INPUT=$(cat)
|
|
||||||
CMD=$(python3 -c "
|
|
||||||
import json, sys
|
|
||||||
try:
|
|
||||||
data = json.loads(sys.stdin.read())
|
|
||||||
print(data.get('tool_input', {}).get('command', ''))
|
|
||||||
except: pass
|
|
||||||
" <<< "$INPUT")
|
|
||||||
BASE="${CLAUDE_PROJECT_DIR:-}"
|
|
||||||
[[ -z "$BASE" ]] && BASE=$(python3 -c "
|
|
||||||
import json, sys
|
|
||||||
try:
|
|
||||||
data = json.loads(sys.stdin.read())
|
|
||||||
print(data.get('cwd', ''))
|
|
||||||
except: pass
|
|
||||||
" <<< "$INPUT")
|
|
||||||
[[ -z "$BASE" ]] && BASE="$(pwd)"
|
|
||||||
|
|
||||||
# Normalize: collapse whitespace, strip leading/trailing
|
|
||||||
CMD_NORM=$(echo "$CMD" | tr -s '[:space:]' ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
||||||
|
|
||||||
deny() {
|
|
||||||
local reason="$1"
|
|
||||||
python3 -c "
|
|
||||||
import json
|
|
||||||
print(json.dumps({
|
|
||||||
'hookSpecificOutput': {
|
|
||||||
'hookEventName': 'PreToolUse',
|
|
||||||
'permissionDecision': 'deny',
|
|
||||||
'permissionDecisionReason': '$reason'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
"
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Dangerous patterns
|
|
||||||
case "$CMD_NORM" in
|
|
||||||
*"rm -rf"*|*"rm -fr"*|*"rm -f -r"*|*"rm -r -f"*) deny "Destructive rm -rf blocked by security hook" ;;
|
|
||||||
*"git reset --hard"*) deny "git reset --hard would lose uncommitted work" ;;
|
|
||||||
*"git push --force"*|*"git push -f"*|*"git push -f "*) deny "git push --force would rewrite history" ;;
|
|
||||||
*"git clean -fd"*|*"git clean -f -d"*) deny "git clean -fd deletes untracked files" ;;
|
|
||||||
*"chmod -R 777"*|*"chmod -R 0777"*) deny "chmod -R 777 is a security risk" ;;
|
|
||||||
*":(){ :"*"};:"*) deny "Fork bomb pattern blocked" ;;
|
|
||||||
*"> /dev/sd"*|*">/dev/sd"*) deny "Block device overwrite blocked" ;;
|
|
||||||
*"mkfs "*|*"mkfs."*) deny "Disk format command blocked" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Block building Rust locally on macOS (should always build on dev server)
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
|
||||||
if echo "$CMD_NORM" | grep -qE '^\s*cargo\s+build'; then
|
|
||||||
# Allow if it's clearly an SSH command (building on remote)
|
|
||||||
if ! echo "$CMD_NORM" | grep -qE 'ssh|sshpass'; then
|
|
||||||
deny "NEVER build Rust on macOS — use ./scripts/deploy-to-target.sh --live or build on dev server via SSH"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for path traversal escaping project root
|
|
||||||
if [[ -n "$BASE" ]] && [[ -d "$BASE" ]]; then
|
|
||||||
if echo "$CMD_NORM" | grep -qE '\.\./|/\.\.'; then
|
|
||||||
if echo "$CMD_NORM" | grep -qE '(rm|mv|cp|cat|chmod|chown)\s+.*\.\.'; then
|
|
||||||
if echo "$CMD_NORM" | grep -qE '\brm\b.*\.\.'; then
|
|
||||||
deny "Path traversal with rm blocked"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# PostToolUse Bash hook: detect deploy commands and remind to test.
|
|
||||||
# Triggers after deploy-to-target.sh runs.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
INPUT=$(cat)
|
|
||||||
|
|
||||||
CMD=$(python3 -c "
|
|
||||||
import json, sys
|
|
||||||
try:
|
|
||||||
data = json.loads(sys.stdin.read())
|
|
||||||
print(data.get('tool_input', {}).get('command', ''))
|
|
||||||
except: pass
|
|
||||||
" <<< "$INPUT")
|
|
||||||
|
|
||||||
# Only trigger on deploy commands or git push
|
|
||||||
if ! echo "$CMD" | grep -qE 'deploy-to-target|git\s+push'; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M')
|
|
||||||
|
|
||||||
python3 -c "
|
|
||||||
import json
|
|
||||||
|
|
||||||
message = '''Deploy detected at $TIMESTAMP.
|
|
||||||
|
|
||||||
Post-deploy checklist:
|
|
||||||
1. Test the web UI at http://192.168.1.228
|
|
||||||
2. Verify modified apps load correctly
|
|
||||||
3. Check backend logs: sudo journalctl -u archipelago -n 20
|
|
||||||
4. Check nginx: sudo tail -f /var/log/nginx/error.log
|
|
||||||
5. If building ISO, sync system configs to image-recipe/configs/
|
|
||||||
6. Update CHANGELOG.md if this is a notable change'''
|
|
||||||
|
|
||||||
output = {
|
|
||||||
'hookSpecificOutput': {
|
|
||||||
'hookEventName': 'PostToolUse',
|
|
||||||
'deployReminder': message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print(json.dumps(output))
|
|
||||||
"
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# PostToolUse Bash hook: detect git push/commit and prompt Claude to update PROGRESS.md.
|
|
||||||
# Returns structured feedback with recent commits so Claude can write a session log entry.
|
|
||||||
# Uses python3 instead of jq for JSON (guaranteed on macOS).
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
INPUT=$(cat)
|
|
||||||
|
|
||||||
# Extract command from JSON using python3
|
|
||||||
CMD=$(python3 -c "
|
|
||||||
import json, sys
|
|
||||||
try:
|
|
||||||
data = json.loads(sys.stdin.read())
|
|
||||||
print(data.get('tool_input', {}).get('command', ''))
|
|
||||||
except: pass
|
|
||||||
" <<< "$INPUT")
|
|
||||||
|
|
||||||
# Only trigger on git push or git commit commands
|
|
||||||
if ! echo "$CMD" | grep -qE '\bgit\s+(push|commit)\b'; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Gather context for the progress update
|
|
||||||
BASE="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
||||||
BRANCH=$(git -C "$BASE" branch --show-current 2>/dev/null || echo "unknown")
|
|
||||||
PROGRESS_FILE="$BASE/PROGRESS.md"
|
|
||||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M')
|
|
||||||
|
|
||||||
# Get recent commits (branch vs main, or last 10)
|
|
||||||
if git -C "$BASE" rev-parse --verify main &>/dev/null; then
|
|
||||||
COMMITS=$(git -C "$BASE" log --oneline main..HEAD 2>/dev/null | head -15)
|
|
||||||
if [ -z "$COMMITS" ]; then
|
|
||||||
COMMITS=$(git -C "$BASE" log --oneline -10 2>/dev/null)
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
COMMITS=$(git -C "$BASE" log --oneline -10 2>/dev/null)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get changed files in recent commits
|
|
||||||
CHANGED_FILES=$(git -C "$BASE" diff --name-only main..HEAD 2>/dev/null | head -20 || \
|
|
||||||
git -C "$BASE" diff --name-only HEAD~5..HEAD 2>/dev/null | head -20 || \
|
|
||||||
echo "unknown")
|
|
||||||
|
|
||||||
# Build the feedback message and output as JSON using python3
|
|
||||||
python3 -c "
|
|
||||||
import json, sys
|
|
||||||
|
|
||||||
message = '''Progress Update Needed
|
|
||||||
|
|
||||||
A git push/commit was detected on branch \`$BRANCH\` at $TIMESTAMP.
|
|
||||||
|
|
||||||
Recent commits:
|
|
||||||
\`\`\`
|
|
||||||
$COMMITS
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
Changed files:
|
|
||||||
\`\`\`
|
|
||||||
$CHANGED_FILES
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
Please update PROGRESS.md:
|
|
||||||
1. Add a session log entry under '## Session Log' with format: ### $TIMESTAMP — $BRANCH
|
|
||||||
2. Summarize what was accomplished (2-4 bullet points based on the commits above)
|
|
||||||
3. Update any roadmap checkboxes if tasks were completed
|
|
||||||
4. Commit the PROGRESS.md update'''
|
|
||||||
|
|
||||||
output = {
|
|
||||||
'hookSpecificOutput': {
|
|
||||||
'hookEventName': 'PostToolUse',
|
|
||||||
'progressUpdate': message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print(json.dumps(output))
|
|
||||||
"
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# PreToolUse Edit|Write guard: block edits outside project and to protected paths.
|
|
||||||
# Denies: paths outside project, .git/, .env*, lockfiles, node_modules/, deploy-config.sh
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
INPUT=$(cat)
|
|
||||||
FILE_PATH=$(python3 -c "
|
|
||||||
import json, sys
|
|
||||||
try:
|
|
||||||
data = json.loads(sys.stdin.read())
|
|
||||||
print(data.get('tool_input', {}).get('file_path', ''))
|
|
||||||
except: pass
|
|
||||||
" <<< "$INPUT")
|
|
||||||
BASE="${CLAUDE_PROJECT_DIR:-}"
|
|
||||||
[[ -z "$BASE" ]] && BASE=$(python3 -c "
|
|
||||||
import json, sys
|
|
||||||
try:
|
|
||||||
data = json.loads(sys.stdin.read())
|
|
||||||
print(data.get('cwd', ''))
|
|
||||||
except: pass
|
|
||||||
" <<< "$INPUT")
|
|
||||||
[[ -z "$BASE" ]] && BASE="$(pwd)"
|
|
||||||
|
|
||||||
# Resolve to absolute path
|
|
||||||
if [[ -z "$FILE_PATH" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
ABS_BASE=$(cd "$BASE" 2>/dev/null && pwd) || true
|
|
||||||
[[ -z "$ABS_BASE" ]] && ABS_BASE=$(python3 -c "import os,sys; print(os.path.abspath(os.path.normpath(sys.argv[1])))" "$BASE" 2>/dev/null) || true
|
|
||||||
[[ -z "$ABS_BASE" ]] && ABS_BASE="$BASE"
|
|
||||||
[[ "$ABS_BASE" != */ ]] && ABS_BASE="${ABS_BASE}/"
|
|
||||||
if [[ "$FILE_PATH" != /* ]]; then
|
|
||||||
ABS_PATH="$ABS_BASE${FILE_PATH#./}"
|
|
||||||
else
|
|
||||||
ABS_PATH="$FILE_PATH"
|
|
||||||
fi
|
|
||||||
ABS_PATH=$(python3 -c "import os,sys; print(os.path.abspath(os.path.normpath(sys.argv[1])))" "$ABS_PATH" 2>/dev/null) || true
|
|
||||||
[[ -z "$ABS_PATH" ]] && ABS_PATH="$ABS_BASE${FILE_PATH#./}"
|
|
||||||
|
|
||||||
deny() {
|
|
||||||
local reason="$1"
|
|
||||||
echo "Blocked: $ABS_PATH — $reason" >&2
|
|
||||||
python3 -c "
|
|
||||||
import json
|
|
||||||
print(json.dumps({
|
|
||||||
'hookSpecificOutput': {
|
|
||||||
'hookEventName': 'PreToolUse',
|
|
||||||
'permissionDecision': 'deny',
|
|
||||||
'permissionDecisionReason': '$reason'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
"
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Protected patterns
|
|
||||||
PROTECTED_PATTERNS=(
|
|
||||||
".git/"
|
|
||||||
".env"
|
|
||||||
".env.local"
|
|
||||||
"node_modules/"
|
|
||||||
"package-lock.json"
|
|
||||||
"scripts/deploy-config.sh"
|
|
||||||
)
|
|
||||||
|
|
||||||
for pattern in "${PROTECTED_PATTERNS[@]}"; do
|
|
||||||
if [[ "$ABS_PATH" == *"$pattern"* ]] || [[ "$ABS_PATH" == *"/$pattern" ]]; then
|
|
||||||
deny "Edit blocked: path matches protected pattern ($pattern)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# .env.*.local
|
|
||||||
if [[ "$ABS_PATH" =~ \.env\..*\.local$ ]]; then
|
|
||||||
deny "Edit blocked: .env.*.local files contain secrets"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure path is under project root
|
|
||||||
if [[ "$ABS_PATH" != "$ABS_BASE"* ]] && [[ "$ABS_PATH" != "$BASE"* ]]; then
|
|
||||||
deny "Edit blocked: path is outside project directory"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
+1
-20
@@ -1,25 +1,6 @@
|
|||||||
{
|
{
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"PreToolUse": [
|
"PreToolUse": [],
|
||||||
{
|
|
||||||
"matcher": "Bash",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-risky-bash.sh"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matcher": "Edit|Write",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"PostToolUse": []
|
"PostToolUse": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
name: add-app
|
|
||||||
description: Step-by-step guide for adding a new containerized app to Archipelago
|
|
||||||
disable-model-invocation: true
|
|
||||||
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
|
||||||
argument-hint: "[app-name]"
|
|
||||||
---
|
|
||||||
|
|
||||||
Add a new containerized app ($ARGUMENTS) to Archipelago.
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
|
|
||||||
### 1. Create the manifest
|
|
||||||
|
|
||||||
Create `apps/{app-id}/manifest.yml` following the spec in `docs/app-manifest-spec.md`:
|
|
||||||
- `app.id` (kebab-case), `app.name`, `app.version` (SemVer)
|
|
||||||
- `container.image` (pinned version, **NEVER** `latest`)
|
|
||||||
- `security`: readonly_root, dropped capabilities, non-root UID > 1000
|
|
||||||
- `health_check`, `dependencies`
|
|
||||||
|
|
||||||
### 2. Add app icon
|
|
||||||
|
|
||||||
Place icon at `neode-ui/public/assets/img/app-icons/{app-id}.{png|webp|svg}`
|
|
||||||
|
|
||||||
### 3. Create status UI (if no native web UI)
|
|
||||||
|
|
||||||
For apps without their own web interface, create a UI container in `docker/{app-id}-ui/` following the patterns in `.cursor/rules/APP-UI-STANDARDS.md`.
|
|
||||||
|
|
||||||
Reference implementations:
|
|
||||||
- Bitcoin UI: `docker/bitcoin-ui/`
|
|
||||||
- LND UI: `docker/lnd-ui/`
|
|
||||||
|
|
||||||
### 4. Update backend
|
|
||||||
|
|
||||||
- Add port mapping in `core/archipelago/src/container/docker_packages.rs`
|
|
||||||
- Add env vars in `get_app_config()` in `core/archipelago/src/api/rpc.rs`
|
|
||||||
|
|
||||||
### 5. Deploy and test
|
|
||||||
|
|
||||||
- Deploy: `./scripts/deploy-to-target.sh --live`
|
|
||||||
- Install from marketplace UI at http://192.168.1.228
|
|
||||||
- Verify it launches and auto-connects to dependencies
|
|
||||||
- Check logs: `sudo podman logs {container-name}`
|
|
||||||
|
|
||||||
### 6. Security review
|
|
||||||
|
|
||||||
- Verify readonly root, dropped caps, non-root user
|
|
||||||
- Check network isolation
|
|
||||||
- No hardcoded secrets
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
name: harden
|
|
||||||
description: Security hardening review and fixes for Archipelago code and infrastructure
|
|
||||||
disable-model-invocation: true
|
|
||||||
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
|
|
||||||
argument-hint: "[area: backend|frontend|containers|scripts|all]"
|
|
||||||
---
|
|
||||||
|
|
||||||
Perform a security hardening pass on $ARGUMENTS (default: all).
|
|
||||||
|
|
||||||
## Backend Hardening (Rust)
|
|
||||||
|
|
||||||
- [ ] No hardcoded credentials — check for Base64-encoded auth strings, passwords in source
|
|
||||||
- [ ] Secrets use `core/security/secrets_manager.rs` — verify encryption is implemented (not plaintext)
|
|
||||||
- [ ] All RPC endpoints validate inputs before processing
|
|
||||||
- [ ] No `unwrap()` on user-supplied data — handle errors gracefully
|
|
||||||
- [ ] Rate limiting on auth endpoints (login, password change)
|
|
||||||
- [ ] Session tokens have proper expiry and rotation
|
|
||||||
- [ ] File permissions: keys at 0o600, dirs at 0o700
|
|
||||||
- [ ] Tracing never logs secrets, passwords, keys, or tokens
|
|
||||||
|
|
||||||
## Frontend Hardening (Vue/TypeScript)
|
|
||||||
|
|
||||||
- [ ] No secrets in source (API keys, passwords, tokens)
|
|
||||||
- [ ] No `eval()` or `innerHTML` with untrusted content
|
|
||||||
- [ ] XSS prevention — sanitize all user inputs
|
|
||||||
- [ ] CSRF protection on state-changing requests
|
|
||||||
- [ ] Credentials use `credentials: 'include'` not localStorage tokens
|
|
||||||
- [ ] No sensitive data in console.log statements
|
|
||||||
|
|
||||||
## Container Hardening
|
|
||||||
|
|
||||||
- [ ] All manifests: `readonly_root: true` (unless documented exception)
|
|
||||||
- [ ] All manifests: capabilities dropped, only required ones added
|
|
||||||
- [ ] All manifests: non-root user (UID > 1000)
|
|
||||||
- [ ] All manifests: `no-new-privileges: true`
|
|
||||||
- [ ] All images pinned to specific versions (no `:latest`)
|
|
||||||
- [ ] Network isolation — no `host` network unless required and documented
|
|
||||||
- [ ] AppArmor profiles defined and enforced
|
|
||||||
|
|
||||||
## Script Hardening
|
|
||||||
|
|
||||||
- [ ] All scripts use `set -euo pipefail`
|
|
||||||
- [ ] No hardcoded passwords (use deploy-config.sh or env vars)
|
|
||||||
- [ ] SSH uses proper key-based auth where possible
|
|
||||||
- [ ] No `chmod 777` or overly permissive permissions
|
|
||||||
- [ ] Temp files use `mktemp` not predictable paths
|
|
||||||
|
|
||||||
Report all findings with file paths and line numbers. Fix issues directly where safe to do so. Flag anything that needs discussion.
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
---
|
|
||||||
name: lint
|
|
||||||
description: Run all linters and type checks for the Archipelago project
|
|
||||||
allowed-tools: Bash, Read, Grep
|
|
||||||
argument-hint: "[backend|frontend|all]"
|
|
||||||
---
|
|
||||||
|
|
||||||
Run linters and type-checks for $ARGUMENTS (default: all).
|
|
||||||
|
|
||||||
## Frontend Linting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd neode-ui
|
|
||||||
|
|
||||||
# Type check
|
|
||||||
npm run type-check 2>&1
|
|
||||||
|
|
||||||
# Check for any `any` types (should be zero)
|
|
||||||
grep -rn ': any' src/ --include='*.ts' --include='*.vue' | grep -v node_modules | grep -v '.d.ts'
|
|
||||||
|
|
||||||
# Check for inline Tailwind violations (long class strings)
|
|
||||||
grep -rn 'class="[^"]\{100,\}"' src/ --include='*.vue'
|
|
||||||
|
|
||||||
# Check for TODO/FIXME
|
|
||||||
grep -rn 'TODO\|FIXME' src/ --include='*.ts' --include='*.vue'
|
|
||||||
|
|
||||||
# Check for console.log (should be cleaned before production)
|
|
||||||
grep -rn 'console\.\(log\|warn\|error\)' src/ --include='*.ts' --include='*.vue' | wc -l
|
|
||||||
```
|
|
||||||
|
|
||||||
## Backend Linting (on dev server)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 \
|
|
||||||
'source ~/.cargo/env && cd ~/archy/core && cargo clippy --all-targets --all-features 2>&1 && cargo fmt --all -- --check 2>&1'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Script Linting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check for scripts missing set -e
|
|
||||||
for f in scripts/*.sh; do
|
|
||||||
if ! head -5 "$f" | grep -q 'set -e'; then
|
|
||||||
echo "MISSING set -e: $f"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check for hardcoded IPs (should use variables)
|
|
||||||
grep -rn '192\.168\.1\.' scripts/ --include='*.sh' | grep -v deploy-config
|
|
||||||
```
|
|
||||||
|
|
||||||
Report all issues found with severity (critical/warning/info).
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
---
|
|
||||||
name: pwa-icon-cache-fix
|
|
||||||
description: Use when the user reports a PWA icon not updating, stale PWA icon, wrong icon after install, or any PWA caching issue. Also applies when changing PWA icons in a Vite + vite-plugin-pwa project.
|
|
||||||
version: 2.0.0
|
|
||||||
---
|
|
||||||
|
|
||||||
# PWA Icon Cache Fix
|
|
||||||
|
|
||||||
## Problem
|
|
||||||
|
|
||||||
PWA icons are cached at FOUR independent layers:
|
|
||||||
1. **Service worker cache** (Workbox precache)
|
|
||||||
2. **Browser HTTP cache**
|
|
||||||
3. **Browser manifest resources** (Chromium stores resized icons in its profile data, keyed by a permanent extension ID tied to the origin — NEVER re-fetched even after uninstall/reinstall)
|
|
||||||
4. **macOS .app bundle** (`.icns` file baked into the `.app` in `~/Applications/`)
|
|
||||||
|
|
||||||
Query string cache busting (`?v=2`) and uninstall/reinstall do NOT fix this. Chromium reuses the same extension ID for the same origin, so it keeps the old cached icons.
|
|
||||||
|
|
||||||
## Fix Steps
|
|
||||||
|
|
||||||
### 1. Verify icon files on disk and server are correct
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Visual check
|
|
||||||
Read packages/app/public/pwa-192x192.png
|
|
||||||
Read packages/app/public/pwa-512x512.png
|
|
||||||
|
|
||||||
# Hash match check
|
|
||||||
curl -s http://localhost:5173/pwa-192x192.png | md5
|
|
||||||
md5 -q packages/app/public/pwa-192x192.png
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Find the PWA's Chromium extension ID
|
|
||||||
|
|
||||||
Read the installed `.app` bundle's `Info.plist` to get the `CrAppModeShortcutID`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
plutil -p "~/Applications/Brave Browser Apps.localized/AIUI.app/Contents/Info.plist" | grep CrAppModeShortcutID
|
|
||||||
```
|
|
||||||
|
|
||||||
This returns an ID like `idemibpphagihbobmgmaojhjfidlfpdl`.
|
|
||||||
|
|
||||||
### 3. Overwrite the cached icons in browser profile
|
|
||||||
|
|
||||||
Chromium stores resized icons at:
|
|
||||||
`~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/Manifest Resources/{ID}/Icons/`
|
|
||||||
|
|
||||||
Overwrite every size using `sips`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ICON_DIR="~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/Manifest Resources/{ID}/Icons"
|
|
||||||
SRC="packages/app/public/pwa-512x512.png"
|
|
||||||
for size in 32 48 64 96 128 192 256 512; do
|
|
||||||
sips -z $size $size "$SRC" --out "${ICON_DIR}/${size}.png"
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Rebuild the macOS .icns in the .app bundle
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ICONSET="/tmp/aiui.iconset"
|
|
||||||
mkdir -p "$ICONSET"
|
|
||||||
SRC="packages/app/public/pwa-512x512.png"
|
|
||||||
sips -z 16 16 "$SRC" --out "$ICONSET/icon_16x16.png"
|
|
||||||
sips -z 32 32 "$SRC" --out "$ICONSET/icon_16x16@2x.png"
|
|
||||||
sips -z 32 32 "$SRC" --out "$ICONSET/icon_32x32.png"
|
|
||||||
sips -z 64 64 "$SRC" --out "$ICONSET/icon_32x32@2x.png"
|
|
||||||
sips -z 128 128 "$SRC" --out "$ICONSET/icon_128x128.png"
|
|
||||||
sips -z 256 256 "$SRC" --out "$ICONSET/icon_128x128@2x.png"
|
|
||||||
sips -z 256 256 "$SRC" --out "$ICONSET/icon_256x256.png"
|
|
||||||
sips -z 512 512 "$SRC" --out "$ICONSET/icon_256x256@2x.png"
|
|
||||||
sips -z 512 512 "$SRC" --out "$ICONSET/icon_512x512.png"
|
|
||||||
cp "$SRC" "$ICONSET/icon_512x512@2x.png"
|
|
||||||
iconutil -c icns "$ICONSET" -o "~/Applications/Brave Browser Apps.localized/AIUI.app/Contents/Resources/app.icns"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Flush macOS icon cache
|
|
||||||
|
|
||||||
```bash
|
|
||||||
touch "~/Applications/Brave Browser Apps.localized/AIUI.app"
|
|
||||||
killall Finder
|
|
||||||
killall Dock
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Bump PWA_CACHE_VERSION in main.ts
|
|
||||||
|
|
||||||
Increment the `PWA_CACHE_VERSION` constant — this nukes all SW caches on next page load for web-layer caching.
|
|
||||||
|
|
||||||
### 7. Delete stale build artifacts
|
|
||||||
|
|
||||||
Remove old `dist/` and `dev-dist/` SW/manifest files.
|
|
||||||
|
|
||||||
## Browser-Specific Paths
|
|
||||||
|
|
||||||
- **Brave**: `~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/`
|
|
||||||
- **Chrome**: `~/Library/Application Support/Google/Chrome/Default/Web Applications/`
|
|
||||||
- **PWA apps (Brave)**: `~/Applications/Brave Browser Apps.localized/`
|
|
||||||
- **PWA apps (Chrome)**: `~/Applications/Chrome Apps.localized/`
|
|
||||||
|
|
||||||
## Key Insight
|
|
||||||
|
|
||||||
Chromium assigns a permanent extension ID per origin (e.g., `localhost:5173`). This ID persists across uninstall/reinstall. The icon cache in `Manifest Resources/{ID}/Icons/` is populated ONCE and never refreshed from the manifest. The only fix is to overwrite the files directly on disk.
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
---
|
|
||||||
name: refactor
|
|
||||||
description: Refactor code for quality, maintainability, and adherence to project standards
|
|
||||||
disable-model-invocation: true
|
|
||||||
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
|
|
||||||
argument-hint: "[file-or-area]"
|
|
||||||
---
|
|
||||||
|
|
||||||
Refactor the specified code ($ARGUMENTS) following Archipelago coding standards.
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
### Rust Backend
|
|
||||||
- [ ] No `unwrap()` or `expect()` — use `?` operator with context
|
|
||||||
- [ ] Replace `#[allow(dead_code)]` — either use it or remove it
|
|
||||||
- [ ] Functions under 50 lines, single responsibility
|
|
||||||
- [ ] Custom error types per module with `thiserror`
|
|
||||||
- [ ] `tracing` for logging — no `println!` or secrets in logs
|
|
||||||
- [ ] Split files over 500 lines into focused modules
|
|
||||||
- [ ] Run `cargo clippy --all-targets --all-features` mentally and fix issues
|
|
||||||
|
|
||||||
### Vue Frontend
|
|
||||||
- [ ] Extract ALL inline Tailwind to global classes in `neode-ui/src/style.css`
|
|
||||||
- [ ] Use semantic class names: `.glass-card`, `.info-card`, `.glass-button`, `.path-option-card`
|
|
||||||
- [ ] Replace ALL `.gradient-button` with `.glass-button` (gradient buttons are BANNED)
|
|
||||||
- [ ] Replace ALL `.gradient-card` / `.gradient-card-dark` with `.glass-card` or `.path-option-card`
|
|
||||||
- [ ] Settings.vue is the gold standard — all screens should match its patterns
|
|
||||||
- [ ] Replace `any` types with proper interfaces or `unknown`
|
|
||||||
- [ ] Ensure `<script setup lang="ts">` on all components
|
|
||||||
- [ ] Remove dead code (unused imports, components like HelloWorld.vue)
|
|
||||||
- [ ] Remove all `TODO`/`FIXME` — fix now or create GitHub issues
|
|
||||||
- [ ] Consolidate `console.log` calls to use a logging utility
|
|
||||||
- [ ] Split views over 800 LOC into sub-components
|
|
||||||
|
|
||||||
### General
|
|
||||||
- [ ] No hardcoded paths (`/Users/dorian/...`)
|
|
||||||
- [ ] No hardcoded credentials — use env vars or secrets manager
|
|
||||||
- [ ] Comment WHY not WHAT
|
|
||||||
- [ ] Remove commented-out code entirely
|
|
||||||
|
|
||||||
After refactoring, verify the code still compiles/type-checks. For frontend: `cd neode-ui && npm run type-check`. Do NOT deploy — leave that to `/deploy`.
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
---
|
|
||||||
name: test
|
|
||||||
description: Run tests or create test coverage for Archipelago
|
|
||||||
disable-model-invocation: true
|
|
||||||
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
|
|
||||||
argument-hint: "[area: backend|frontend|all] or [specific-file]"
|
|
||||||
---
|
|
||||||
|
|
||||||
Run or create tests for $ARGUMENTS.
|
|
||||||
|
|
||||||
## Backend Testing (Rust)
|
|
||||||
|
|
||||||
### Run existing tests
|
|
||||||
```bash
|
|
||||||
# On dev server (never build Rust on macOS)
|
|
||||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 \
|
|
||||||
'source ~/.cargo/env && cd ~/archy/core && cargo test --all-features 2>&1'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating new tests
|
|
||||||
- Place unit tests in the same file with `#[cfg(test)]` module
|
|
||||||
- Place integration tests in `core/{crate}/tests/`
|
|
||||||
- Use `#[tokio::test]` for async tests
|
|
||||||
- Mock external dependencies (filesystem, network, Podman)
|
|
||||||
- Test error cases, not just happy paths
|
|
||||||
- Aim for >80% coverage on core logic
|
|
||||||
|
|
||||||
### Priority areas needing tests
|
|
||||||
1. RPC endpoint handlers (core/archipelago/src/api/)
|
|
||||||
2. Manifest parsing (core/container/src/manifest.rs)
|
|
||||||
3. Dependency resolver (core/container/src/dependency_resolver.rs)
|
|
||||||
4. Auth flows (core/archipelago/src/auth.rs)
|
|
||||||
5. Secrets manager (core/security/src/secrets_manager.rs)
|
|
||||||
6. Port allocation (core/container/src/port_manager.rs)
|
|
||||||
|
|
||||||
## Frontend Testing (Vue/TypeScript)
|
|
||||||
|
|
||||||
### Setup (if not already configured)
|
|
||||||
Ensure vitest is configured in `neode-ui/`:
|
|
||||||
```bash
|
|
||||||
cd neode-ui && npm run test 2>&1 || echo "No test script configured"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating new tests
|
|
||||||
- Use Vitest + @vue/test-utils
|
|
||||||
- Place tests in `neode-ui/src/__tests__/` or co-located `*.test.ts`
|
|
||||||
- Test stores (Pinia) with `createTestingPinia()`
|
|
||||||
- Test API clients with mocked fetch
|
|
||||||
- Test component rendering and interactions
|
|
||||||
- Test routing guards
|
|
||||||
|
|
||||||
### Priority areas needing tests
|
|
||||||
1. Pinia stores (app.ts, container.ts, appLauncher.ts)
|
|
||||||
2. RPC client (api/rpc-client.ts) — error handling, retry logic
|
|
||||||
3. WebSocket client (api/websocket.ts) — reconnection
|
|
||||||
4. Router guards — auth flow, session timeout
|
|
||||||
5. Key components — ContainerStatus, SpotlightSearch
|
|
||||||
|
|
||||||
Report test results and any new tests created.
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
---
|
|
||||||
name: ux-review
|
|
||||||
description: Review UI components against Archipelago glassmorphism design standards and UX conventions
|
|
||||||
disable-model-invocation: true
|
|
||||||
allowed-tools: Read, Glob, Grep, Edit, Write
|
|
||||||
argument-hint: "[component-or-view-name]"
|
|
||||||
---
|
|
||||||
|
|
||||||
Review the UI of $ARGUMENTS against Archipelago's glassmorphism design system and UX standards.
|
|
||||||
|
|
||||||
## Design System Compliance
|
|
||||||
|
|
||||||
### Glass Classes (must use global classes from style.css)
|
|
||||||
- [ ] Section containers use `.path-option-card cursor-default px-6 py-6` (Settings-style sections)
|
|
||||||
- [ ] Content containers/modals use `.glass-card`
|
|
||||||
- [ ] Interactive selectable cards use `.path-option-card` (with hover)
|
|
||||||
- [ ] Status displays use `.info-card` (no hover effects)
|
|
||||||
- [ ] ALL buttons use `.glass-button` — NEVER `.gradient-button` (BANNED)
|
|
||||||
- [ ] Large primary actions use `.path-action-button`
|
|
||||||
- [ ] Info sub-cards use `bg-black/20 rounded-xl border border-white/10`
|
|
||||||
- [ ] Info rows use `bg-white/5 rounded-lg` pattern
|
|
||||||
- [ ] Action buttons in info sections use `.info-card-button`
|
|
||||||
|
|
||||||
### BANNED — Flag These as Violations
|
|
||||||
- [ ] No `.gradient-button` anywhere (replace with `.glass-button`)
|
|
||||||
- [ ] No `.gradient-card` / `.gradient-card-dark` (replace with `.glass-card` or `.path-option-card`)
|
|
||||||
|
|
||||||
### NO Inline Tailwind
|
|
||||||
- [ ] Check for long `class="..."` strings with layout/color utilities
|
|
||||||
- [ ] Extract to semantic classes in `neode-ui/src/style.css`
|
|
||||||
- [ ] Name classes semantically: `.app-card`, `.status-badge`, `.nav-item`
|
|
||||||
|
|
||||||
### Color Compliance
|
|
||||||
- [ ] Primary text: `text-white/90` (not `text-white` or arbitrary opacity)
|
|
||||||
- [ ] Muted text: `text-white/60` to `text-white/70`
|
|
||||||
- [ ] Backgrounds: `rgba(0,0,0,0.60)` with `backdrop-filter: blur(24px)`
|
|
||||||
- [ ] Borders: `rgba(255,255,255,0.18)` standard
|
|
||||||
- [ ] Status colors: green=#4ade80, red=#ef4444, yellow=#facc15, blue=#3b82f6, orange=#fb923c
|
|
||||||
|
|
||||||
### Typography
|
|
||||||
- [ ] Font: Avenir Next (body), Montserrat (headings via `font-archipelago`)
|
|
||||||
- [ ] H1: text-3xl font-bold, H2: text-2xl font-semibold, H3: text-xl font-semibold
|
|
||||||
- [ ] Body: text-base, Small: text-sm, Labels: text-xs
|
|
||||||
|
|
||||||
### Interaction States
|
|
||||||
- [ ] Hover: `translateY(-2px)` lift + background brighten + enhanced shadow
|
|
||||||
- [ ] Active: `translateY(1px)` press
|
|
||||||
- [ ] Selected: brighter background + glow shadow + enhanced gradient border
|
|
||||||
- [ ] Disabled: reduced opacity (~50%), no pointer events
|
|
||||||
- [ ] Loading: spinner SVG + descriptive text, button disabled
|
|
||||||
- [ ] Focus-visible: soft blue glow `rgba(120, 180, 255, 0.2)`
|
|
||||||
|
|
||||||
### Transitions
|
|
||||||
- [ ] Standard: `all 0.3s ease`
|
|
||||||
- [ ] All interactive elements have transitions (no jarring state changes)
|
|
||||||
- [ ] Respect `prefers-reduced-motion`
|
|
||||||
|
|
||||||
### Spacing
|
|
||||||
- [ ] 4px grid system (p-1=4px, p-2=8px, p-3=12px, p-4=16px)
|
|
||||||
- [ ] 16px default padding on cards
|
|
||||||
- [ ] Consistent gap values between grid items
|
|
||||||
|
|
||||||
### Responsive
|
|
||||||
- [ ] Mobile: single column, reduced padding, touch targets >= 44x44px
|
|
||||||
- [ ] Tablet (md:): two columns
|
|
||||||
- [ ] Desktop (lg:): three columns, full effects
|
|
||||||
|
|
||||||
### Accessibility
|
|
||||||
- [ ] Semantic HTML (`<button>`, `<nav>`, `<main>`, not div soup)
|
|
||||||
- [ ] ARIA labels on icon-only buttons
|
|
||||||
- [ ] Keyboard navigable (Tab order, Enter to activate, Esc to close)
|
|
||||||
- [ ] Color contrast WCAG AA (4.5:1 normal text, 3:1 large)
|
|
||||||
- [ ] Images have alt text (decorative: `alt=""`)
|
|
||||||
|
|
||||||
### Icons
|
|
||||||
- [ ] Stroke-based SVGs, stroke-width 2.5 default
|
|
||||||
- [ ] Color: `text-white/85` default, `text-white` on hover
|
|
||||||
- [ ] Drop-shadow filter applied on interactive icons
|
|
||||||
- [ ] Size: w-5 h-5 standard, w-4 h-4 small
|
|
||||||
|
|
||||||
## Service UI Review (if reviewing docker/*-ui/)
|
|
||||||
- [ ] Uses `.glass-card` for main sections
|
|
||||||
- [ ] Uses `.info-card` for status (no hover)
|
|
||||||
- [ ] Uses `.info-card-button` for actions (with hover)
|
|
||||||
- [ ] Uses `bg-white/5` for info rows
|
|
||||||
- [ ] Header: logo + title + description + status
|
|
||||||
- [ ] Background image loads correctly
|
|
||||||
- [ ] Mobile responsive
|
|
||||||
|
|
||||||
Report violations with file paths and specific fixes.
|
|
||||||
Reference in New Issue
Block a user