From selran-hub
Use this skill when creating or updating start/stop scripts for any app with a backend+frontend, or when the user asks to "start the app", "run the app", "fix port conflicts", "make ports dynamic", or set up app launch scripts. Handles safe process cleanup, dynamic port allocation via the shared port registry service, and verified process identity. Also use when scaffolding a new app's start.sh/stop.sh.
How this skill is triggered — by the user, by Claude, or both
Slash command
/selran-hub:app-startupThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
These rules apply to EVERY app, EVERY time, with NO exceptions:
These rules apply to EVERY app, EVERY time, with NO exceptions:
127.0.0.1:11999. No hardcoded ports. No fallback ranges. No "just use 8000". The CLI wrapper app-port-registry talks to the same backend.~/.config/app-ports.json directly. The internal SQLite store is a private implementation detail. All access goes through the service or CLI only.ensure on startup to get its assigned ports. ensure is create-if-missing: if the app ID or canonical path is already registered, it returns the existing assignment without allocating a new block.Endpoint: http://127.0.0.1:11999
CLI wrapper: app-port-registry (same backend)
Single source of truth for port assignments across ALL apps and ALL tools (Claude Code, Codex, manual scripts, any AI agent).
| Method | Endpoint | Description |
|---|---|---|
GET | /health | Health check |
GET | /v1/report | List all registered apps |
GET | /v1/apps/<app_id> | Get a specific app's registration |
GET | /v1/lookup?path=/absolute/path | Look up an app by its canonical path |
POST | /v1/ensure | Register (create-if-missing) an app |
POST /v1/ensure — Request Body{
"app_id": "my-app",
"path": "/absolute/path/to/app",
"description": "My App — what it does"
}
Behavior: If app_id already exists, or if path is already registered, returns the existing assignment. Otherwise allocates the next available 5-port block.
app-port-registry list --json
app-port-registry get <app-id> --json
app-port-registry lookup <absolute-path> --json
app-port-registry ensure <app-id> <absolute-path> --description "..." --json
ensure or get){
"app_id": "my-app",
"range": [12000, 12004],
"path": "/absolute/path/to/app",
"description": "My App — what it does"
}
Call ensure to get the app's assigned port block. This is idempotent — safe to call every startup.
resolve_ports() {
# Try service first
RESULT=$(curl -sf http://127.0.0.1:11999/v1/ensure \
-H "Content-Type: application/json" \
-d "{\"app_id\": \"$APP_ID\", \"path\": \"$SCRIPT_DIR\", \"description\": \"$APP_DESCRIPTION\"}" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$RESULT" ]; then
PORT_START=$(echo "$RESULT" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['range'][0])")
PORT_END=$(echo "$RESULT" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['range'][1])")
else
# Fallback: CLI wrapper
RESULT=$(app-port-registry ensure "$APP_ID" "$SCRIPT_DIR" --description "$APP_DESCRIPTION" --json 2>/dev/null)
PORT_START=$(echo "$RESULT" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['range'][0])")
PORT_END=$(echo "$RESULT" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['range'][1])")
fi
if [ -z "$PORT_START" ]; then
echo "ERROR: Could not resolve ports from registry service" >&2
exit 1
fi
}
Three layers of cleanup, in order:
.app.pid from the app's root directorypgrep -f "uvicorn.*app.main:app" → verify cwd/command contains this app's pathpgrep -f "vite" → verify cwd/command contains this app's pathlsof -p PID | grep cwd | awk '{for(i=9;i<=NF;i++) printf "%s ", $i}' for paths with spacespkill -f "uvicorn" — kills ALL uvicorn instancespkill -f "vite" — kills ALL vite instancesnode, python, npmBackend always on slot 0 (PORT_START), frontend always on slot 1 (PORT_START + 1). No scanning, no fallback.
BACKEND_PORT=$PORT_START
FRONTEND_PORT=$((PORT_START + 1))
cat > frontend/vite.config.dynamic.js << EOF
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
port: $FRONTEND_PORT,
strictPort: true,
proxy: {
'/api': {
target: 'http://localhost:$BACKEND_PORT',
changeOrigin: true,
}
}
}
})
EOF
--port $BACKEND_PORT--config vite.config.dynamic.js.app.pidcleanup() {
kill $BACKEND_PID $FRONTEND_PID 2>/dev/null
rm -f "$PID_FILE"
rm -f frontend/vite.config.dynamic.js
}
trap cleanup INT TERM
wait
Every app MUST have:
app-root/
├── start.sh — Smart startup with registry service lookup
├── stop.sh — Verified shutdown (reads registry for port range)
├── .app.pid — Runtime PID file (gitignored)
├── .gitignore — Must include .app.pid and dynamic configs
.app.pid
frontend/vite.config.dynamic.js
lsof is available on macOS and Linuxnetstat -ano | findstr :PORT and tasklist /FI "PID eq ..." equivalentsSearches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.
npx claudepluginhub apourmd941/selran-hub --plugin selran-hub