diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 86641a8..77e4362 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -9,6 +9,7 @@ "homepage": "https://www.purchasely.com", "repository": "https://github.com/Purchasely/Purchasely-AI-Plugin", "license": "MIT", + "hooks": "./hooks/hooks.json", "keywords": [ "purchasely", "sdk", diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d4140..ffe846e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ All notable changes to this project are documented here. The format is based on - `gemini-extension.json` at the repository root — unlocks one-shot install via `gemini extensions install https://github.com/Purchasely/Purchasely-AI-Plugin`. - OpenCode plugin support via `.opencode/INSTALL.md` — covers prerequisites, the one-line `opencode.json` install, usage examples, updating, and Windows troubleshooting (`npm install --prefix` workaround). - README *OpenCode* block in the per-harness Quickstart section pointing at `.opencode/INSTALL.md`. +- `SessionStart` hook (`hooks/hooks.json`, `hooks/hooks-cursor.json`, `hooks/session-start`, `hooks/run-hook.cmd`, `hooks/intro.md`) auto-injects pointers to the `integrate` / `review` / `debug` skills and `/purchasely:question` command so the plugin is discoverable without the user typing a slash command first. Works on Claude Code, Cursor, and any host honoring the standard SDK `additionalContext` envelope. Zero-dependency POSIX shell + polyglot `.cmd` wrapper for Windows (Git Bash / WSL). +- `.claude-plugin/plugin.json` now points at `./hooks/hooks.json`. ### Changed diff --git a/hooks/hooks-cursor.json b/hooks/hooks-cursor.json new file mode 100644 index 0000000..9f89e90 --- /dev/null +++ b/hooks/hooks-cursor.json @@ -0,0 +1,8 @@ +{ + "version": 1, + "hooks": { + "sessionStart": [ + { "command": "./hooks/run-hook.cmd session-start" } + ] + } +} diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..79d8cee --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup|clear|compact", + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start", + "async": false + } + ] + } + ] + } +} diff --git a/hooks/intro.md b/hooks/intro.md new file mode 100644 index 0000000..1487658 --- /dev/null +++ b/hooks/intro.md @@ -0,0 +1,15 @@ +# Purchasely AI Plugin is available + +This project is using the **Purchasely AI Plugin**. You have access to three task-scoped skills and one free-form Q&A command that cover the Purchasely SDK across iOS, Android, React Native, Flutter, and Cordova. + +## Skills (auto-invoked when relevant) + +- **`integrate`** — step-by-step SDK integration: install, `Purchasely.start(...)`, paywall display via `fetchPresentation(...)`, action interceptor, user login/logout, Restore, Manage Subscription, plus campaigns / promo offers / analytics. +- **`review`** — 24-point checklist that audits an existing integration for missing `processAction(true)` branches, deprecated APIs, identity ordering, `PrivacyInfo.xcprivacy`, Google Play Billing v8, log-level gating, and more. +- **`debug`** — diagnostic flow for blank paywalls, frozen UI, purchase failures, and deeplinks. Includes SDK debug logging (Step 0), `PLYError` decoding (Step 6), and the screen-issue-report escalation template (Step 5). + +## Slash command + +- **`/purchasely:question`** — free-form SDK Q&A. Routes to the `sdk-expert` agent for anything that doesn't fit the three skills above. + +When the user mentions Purchasely, paywalls, subscriptions, `PLYPresentation`, `userLogin`, or related concepts, load the matching skill before answering. The wrapper class pattern (`PurchaselyService`, `IAPManager`, …) is a **recommendation**, not a requirement — direct `Purchasely.*` calls anywhere in the app are fully supported. diff --git a/hooks/run-hook.cmd b/hooks/run-hook.cmd new file mode 100755 index 0000000..b213732 --- /dev/null +++ b/hooks/run-hook.cmd @@ -0,0 +1,44 @@ +:; # -*- mode: sh -*- +:; # Polyglot wrapper: runs as cmd.exe on Windows and as /bin/sh elsewhere. +:; # On POSIX shells the leading ':;' lines are no-ops; the cmd-side @goto +:; # jumps past them into the Windows branch. +:; +:; # --- POSIX branch --------------------------------------------------------- +:; DIR=$(cd -- "$(dirname -- "$0")" && pwd) +:; HOOK_NAME=${1:-session-start} +:; shift 2>/dev/null || true +:; if [ -x "$DIR/$HOOK_NAME" ]; then +:; exec "$DIR/$HOOK_NAME" "$@" +:; elif [ -f "$DIR/$HOOK_NAME" ]; then +:; exec sh "$DIR/$HOOK_NAME" "$@" +:; else +:; echo "run-hook.cmd: hook not found: $DIR/$HOOK_NAME" >&2 +:; exit 1 +:; fi +:; exit 0 + +@echo off +setlocal +set "DIR=%~dp0" +set "HOOK_NAME=%~1" +if "%HOOK_NAME%"=="" set "HOOK_NAME=session-start" +shift +if exist "%DIR%%HOOK_NAME%" ( + where sh >nul 2>nul + if %ERRORLEVEL%==0 ( + sh "%DIR%%HOOK_NAME%" %* + exit /b %ERRORLEVEL% + ) else ( + where bash >nul 2>nul + if %ERRORLEVEL%==0 ( + bash "%DIR%%HOOK_NAME%" %* + exit /b %ERRORLEVEL% + ) else ( + echo run-hook.cmd: no POSIX shell on PATH; install Git Bash or WSL. 1>&2 + exit /b 1 + ) + ) +) else ( + echo run-hook.cmd: hook not found: %DIR%%HOOK_NAME% 1>&2 + exit /b 1 +) diff --git a/hooks/session-start b/hooks/session-start new file mode 100755 index 0000000..27c83c9 --- /dev/null +++ b/hooks/session-start @@ -0,0 +1,53 @@ +#!/bin/sh +# Purchasely AI Plugin — SessionStart hook +# +# Emits a short pointer to the plugin's skills + slash command so the host +# (Claude Code, Cursor, Copilot CLI, …) knows the Purchasely playbook is +# available without the user having to type a slash command first. +# +# Output format is chosen at runtime: +# - Claude Code -> {"hookSpecificOutput": {"hookEventName": "SessionStart", +# "additionalContext": "..."}} +# - Cursor -> {"additional_context": "..."} +# - Standard SDK -> {"additionalContext": "..."} (top-level) +# +# Zero runtime dependencies: pure /bin/sh, no jq, no node. + +set -eu + +DIR=$(cd -- "$(dirname -- "$0")" && pwd) +INTRO_FILE="$DIR/intro.md" + +if [ ! -f "$INTRO_FILE" ]; then + echo "session-start: missing $INTRO_FILE" >&2 + exit 1 +fi + +# JSON-escape stdin (\, ", control chars, newlines) without external tools. +json_escape() { + awk ' + BEGIN { ORS = "" } + { + s = $0 + gsub(/\\/, "\\\\", s) + gsub(/"/, "\\\"", s) + gsub(/\r/, "\\r", s) + gsub(/\t/, "\\t", s) + printf "%s\\n", s + } + ' "$1" +} + +ESCAPED=$(json_escape "$INTRO_FILE") + +# Platform detection. +# CLAUDECODE / CLAUDE_PLUGIN_ROOT -> Claude Code +# CURSOR_AGENT / CURSOR_TRACE_ID -> Cursor +# anything else -> generic SDK envelope +if [ "${CLAUDECODE:-}" = "1" ] || [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then + printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$ESCAPED" +elif [ -n "${CURSOR_AGENT:-}" ] || [ -n "${CURSOR_TRACE_ID:-}" ]; then + printf '{"additional_context":"%s"}\n' "$ESCAPED" +else + printf '{"additionalContext":"%s"}\n' "$ESCAPED" +fi