Skill: houyongsheng/userlayer (version 0.1.1)

--- name: user-layer description: Use this skill when the user wants a UserLayer report for an App Store or Google Play app, including full analysis, polling, and follow-up questions grounded in real reviews. Trigger it for competitor review analysis, user pain point extraction, segment discovery, and opportunity validation tasks. homepage: https://github.com/houyongsheng/userlayer metadata: clawdbot: requires: env: - API_KEY primaryEnv: API_KEY optionalEnv: - LAUNCHBASE_API_URL files: - scripts/main.py - scripts/call_api.py --- # UserLayer UserLayer turns App Store and Google Play reviews into structured research outputs: - pain points - user segments - market opportunities - follow-up answers grounded in cited reviews Use the bundled wrappers in `scripts/main.py`. Do not hand-roll raw HTTP requests when this skill is available. ## Use This Skill When - The user gives you an App Store or Google Play URL and wants review-backed insights. - The user wants to ask follow-up questions about a completed full analysis. ## Prerequisites - `API_KEY` must be set to a valid UserLayer API key. - `LAUNCHBASE_API_URL` is optional. Default: `https://lb-api.workflowhunt.com` ## Credentials and Host - Primary credential: `API_KEY` - Default API host: `https://lb-api.workflowhunt.com` - `API_KEY` should be scoped only for UserLayer API access and treated as a paid production credential. ## Available Wrappers - `analyze(app_url: str, max_reviews: int | None = None)` - `check_status(analysis_id: str)` - `query(pain_point_id: str, question: str)` ## Typical Flow 1. Call `analyze()` to start a full async run. 2. Poll with `check_status()` until the report is complete. 3. Read the returned `pain_points`. 4. Use a `pain_point.id` with `query()` for targeted follow-up questions. You do not need a separate preview step. `analyze()` is the main public entry point. ## Pricing Baseline - One `analyze()` run costs `$2.99` and includes retrieval plus full analysis of the latest `100` reviews. - If `max_reviews` is raised above `100`, extra reviews are billed as add-ons at `$0.01` per extra review. - `query()` is billed at `$0.01 / 1K tokens`. ## Key Response Shapes - `analyze()` starts an async job and returns a lightweight payload with: - `success` - `data.analysis_id` - `data.status` - `check_status()` returns the full completed report when ready, including: - `data.pain_points` - `data.user_segments` - `data.opportunities` - `sources` - `usage` - `query()` returns a follow-up answer for a specific pain point, including: - `data.answer` - `data.confidence` - `sources` - `usage` Treat `pain_points[].id` from a completed analysis as the required input for `query()`. ## Operating Rules - Default review count is `100` latest reviews. - `analyze()` includes retrieval and analysis of `100` latest reviews by default. - If `max_reviews` is raised above `100`, extra reviews are billed as add-ons. - `analyze()` is asynchronous and returns an `analysis_id`; poll with `check_status()`. - Use `query()` only after a completed full analysis returns a valid `pain_point_id`. - Treat `sources` and cited review evidence as the source of truth. - If `DATA_INDEX_NOT_FOUND` is returned, rerun a full analysis before querying again.

path: scripts/main.py
content: import os
import requests
from typing import Dict, Any

from .call_api import call_api

LAUNCHBASE_API_URL = os.environ.get("LAUNCHBASE_API_URL", "https://lb-api.workflowhunt.com")
SERVICE_NAME = "userlayer"

def analyze(app_url: str, max_reviews: int | None = None) -> Dict[str, Any]:
    """
    Submit a full async analysis job.
    Default review count is 100 latest reviews. Callers may explicitly raise it when they want broader coverage.
    """
    params: Dict[str, Any] = {"app_url": app_url}
    if max_reviews is not None:
        params["max_reviews"] = max_reviews

    return call_api(
        service=SERVICE_NAME,
        endpoint="/analyze",
        params=params,
    )

def query(pain_point_id: str, question: str) -> Dict[str, Any]:
    """
    针对已分析生成的痛点进行追问。
    基于 pgvector 语义检索提供回答及引用来源。
    """
    return call_api(
        service=SERVICE_NAME,
        endpoint="/query",
        params={"pain_point_id": pain_point_id, "question": question},
    )

def check_status(analysis_id: str) -> Dict[str, Any]:
    """
    Poll async analysis status.
    """
    [REDACTED_SECRET]"API_KEY")
    if not [REDACTED_SECRET] {"success": False, "error": "AUTH_NO_API_KEY"}
        
    try:
        response = requests.get(
            f"{LAUNCHBASE_API_URL}/v1/{SERVICE_NAME}/analyze/{analysis_id}/status",
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=30
        )
        if response.status_code != 200:
            try:
                err_data = response.json()
                if "error" in err_data:
                    return err_data
            except Exception:
                pass
            response.raise_for_status()
            
        return response.json()
    except Exception as e:
        return {"success": False, "error": str(e)}


path: scripts/call_api.py
content: """
LaunchBase 通用 API 调用模块
版本：1.0.0
警告：此文件由 LaunchBase 官方提供，禁止修改。如需定制请在 main.py 中处理。
"""
import os
import requests

LAUNCHBASE_API_URL = os.environ.get(
    "LAUNCHBASE_API_URL",
    "https://lb-api.workflowhunt.com"  # 默认网关地址，可通过环境变量覆盖
)

def call_api(
    service: str,
    endpoint: str,
    params: dict,
) -> dict:
    """
    调用 LaunchBase API 网关的通用方法。
    自动读取 API_KEY 环境变量，处理鉴权和错误。

    Args:
        service: 业务服务名称，如 "userlayer"
        endpoint: 业务接口路径，如 "/analyze"
        params: 业务入参

    Returns:
        符合 LaunchBase 标准响应格式的字典
    """
    [REDACTED_SECRET]"API_KEY")
    if not [REDACTED_SECRET] {
            "success": False,
            "error": "AUTH_NO_API_KEY: API_KEY environment variable not set.",
            "data": {},
            "usage": {"tokens": 0, "cost": 0},
            "sources": [],
        }

    try:
        response = requests.post(
            f"{LAUNCHBASE_API_URL}/v1/{service}{endpoint}",
            json=params,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
            },
            timeout=60,
        )
        # Note: we use raise_for_status to catch 4xx and 5xx errors from the gateway
        # if the gateway responded with a structured error, we might want to return that instead.
        if response.status_code != 200:
            try:
                err_data = response.json()
                if "error" in err_data:
                    return err_data
            except Exception:
                pass
            response.raise_for_status()
            
        return response.json()

    except requests.exceptions.Timeout:
        return {
            "success": False,
            "error": "SERVICE_TIMEOUT: Request timed out after 60 seconds.",
            "data": {},
            "usage": {"tokens": 0, "cost": 0},
            "sources": [],
        }
    except requests.exceptions.RequestException as e:
        return {
            "success": False,
            "error": f"SERVICE_REQUEST_ERROR: {str(e)}",
            "data": {},
            "usage": {"tokens": 0, "cost": 0},
            "sources": [],
        }


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)