Skill: imluyu/jobclaw-recruit (version 1.0.0)

--- name: recruiter description: "Help recruiters publish job postings to the job matching system. Use when users want to: (1) post a job, (2) publish a position, (3) hire someone, (4) recruit candidates, (5) find employees, or (6) advertise job openings. Supports flexible information collection - users can provide all details at once or be guided through step-by-step. Automatically creates recruiter account, generates job vectors, and enables AI-powered candidate matching." --- # Recruiter Publish, update, and manage job postings in the AI-powered job matching system, and view matched candidates. ## Overview This skill helps recruiters manage job postings through an interactive conversation. Provide information flexibly - share everything at once or answer questions step-by-step. The system supports: 1. **Publish job** - Create a recruiter account, publish a job posting, and trigger AI matching 2. **Update job** - Modify job details (title, requirements, salary, etc.) 3. **Delete job** - Soft-delete job posting (mark as INACTIVE, preserving match history) 4. **View jobs** - Check all your published jobs 5. **List matched candidates** - View candidates matched by the AI system with similarity scores ## Available Scripts - **publish_job.py** - Publish, update, delete jobs, and list matches for a specific job - **get_profile.py** - View all your jobs and matched candidates (read-only) ## Workflow ### Publish Job (action: publish) #### Step 1: Gather Job Posting Information Collect the following required fields. Users can provide them in any order or all at once: **Required fields:** - **Job title**: Position name (e.g., "Senior Python Backend Engineer") - **Company name**: Employer name - **Job requirements**: Detailed requirements including skills, responsibilities, and qualifications - **Salary range**: Compensation range (e.g., "25k-40k", "30k-50k") - **Work location**: Office location (e.g., "Shanghai-Changning District", "Beijing-Chaoyang District") - **Job type**: Employment type (e.g., "Full-time", "Part-time", "Contract") - **Education requirement**: Minimum education level (e.g., "Bachelor's degree or above") - **Experience requirement**: Required years of experience (e.g., "3-5 years", "5+ years") **Example user inputs:** _All at once:_ > "I want to post a job for a Python Backend Engineer at Pinduoduo in Shanghai Changning District. Salary 25k-40k. Requirements: Familiar with Python, Django/Flask frameworks, RESTful API development experience. Knowledge of MySQL, Redis databases. E-commerce or payment system experience preferred. Full-time position, bachelor's degree or above, 3-5 years experience." _Step by step:_ > "I need to hire a developer" > [Claude asks for job title] > "Python Backend Engineer" > [Claude asks for company, requirements, salary, location, etc.] #### Step 2: Validate Completeness Before submission, verify all required fields are present. If any are missing, ask the user to provide them. #### Step 3: Publish Job Posting ```bash cat <<EOF | python3 scripts/publish_job.py { "action": "publish", "title": "<job title>", "companyName": "<company name>", "requirement": "<detailed requirements>", "salary": "<salary range>", "location": "<work location>", "jobType": "<employment type>", "education": "<education requirement>", "experience": "<experience requirement>", "status": "ACTIVE" } EOF ``` #### Step 4: Confirm Success After successful publication, inform the user and **save the returned job ID** for future operations (update, delete, list matches). The token is automatically saved. --- ### Update Job (action: update) Requires the **jobId** from a previous publish. Only changed fields need to be provided. The script will automatically use the saved token. ```bash cat <<EOF | python3 scripts/publish_job.py { "action": "update", "jobId": "<job id>", "salary": "<new salary range>", "requirement": "<updated requirements>" } EOF ``` Updatable fields: `title`, `companyName`, `requirement`, `salary`, `location`, `jobType`, `education`, `experience`, `status`. --- ### Delete Job (action: delete) Soft-deletes the job posting by marking it as INACTIVE. Match history is preserved. ```bash cat <<EOF | python3 scripts/publish_job.py { "action": "delete", "jobId": "<job id>" } EOF ``` --- ### View Jobs and Matches (get_profile.py) Check your published jobs and matched candidates without making any changes. #### View All Jobs ```bash cat <<EOF | python3 scripts/get_profile.py { "action": "jobs" } EOF ``` #### View Specific Job Details ```bash cat <<EOF | python3 scripts/get_profile.py { "action": "job", "jobId": "<job id>" } EOF ``` #### View Matches for Specific Job ```bash cat <<EOF | python3 scripts/get_profile.py { "action": "matches", "jobId": "<job id>" } EOF ``` #### View All Matches Across All Jobs ```bash cat <<EOF | python3 scripts/get_profile.py { "action": "all-matches" } EOF ``` #### View Full Information (all jobs + all matches) ```bash cat <<EOF | python3 scripts/get_profile.py { "action": "full" } EOF ``` **When to use get_profile.py:** - User asks "What jobs have I published?" or "Show me my jobs" - User wants to check matches across all jobs - User wants to review job details before updating - User asks "Do I have any candidates?" --- ### List Matched Candidates (action: matches) Retrieve candidates matched by the AI system for a specific job posting and provide comprehensive multi-dimensional analysis. ```bash cat <<EOF | python3 scripts/publish_job.py { "action": "matches", "jobId": "<job id>" } EOF ``` #### Step 1: Retrieve Matched Candidates The API returns a list of matched candidates with similarity scores. Each match includes: - Candidate details (name, resume, skills, experience, etc.) - Similarity score (0-1 range, based on vector matching) - Match metadata #### Step 2: Provide Comparative Summary After analyzing individual candidates, provide a comparative summary: **Top 3 Recommendations:** Rank the top 3 candidates with brief rationale for each. **Candidate Distribution:** - Excellent matches (score > 0.85): X candidates - Good matches (score 0.75-0.85): Y candidates - Moderate matches (score 0.65-0.75): Z candidates **Hiring Strategy Advice:** - Which candidates to prioritize for interviews - Suggested interview panel composition - Timeline recommendations - Backup candidate strategy #### Output Format Guidelines **IMPORTANT: Always respond in the user's language.** If the user communicates in Chinese, respond in Chinese. If in English, respond in English. Adapt all section headers, labels, and content to match the user's language. **Structure your analysis report as follows:** **Report Header:** - Title indicating this is a candidate match analysis report - Job position and company name - Visual separators (lines, emojis) to organize sections **For Each Matched Candidate:** 1. **Candidate Header Section** - Candidate name/identifier and number - Visual separator line 2. **Overall Match Score** (📈) - Display the similarity score (e.g., 0.89) with interpretation (excellent/good/moderate/fair) - Brief summary of why this candidate matches or doesn't match 3. **Skill Alignment Analysis** (🔧) - ✅ List matching skills with experience levels - 💡 Highlight bonus skills (beyond requirements) - ⚠️ Identify skill gaps (required but missing) - Provide skill match percentage estimate 4. **Experience Fit Analysis** (💼) - Compare required vs. actual years of experience - Assess industry/domain experience relevance - Evaluate project complexity and scale alignment - Determine seniority level match - Review career progression trajectory 5. **Education & Qualifications** (🎓) - Education level match - Relevant certifications - Academic background relevance 6. **Cultural & Team Fit** (🤝) - Work style indicators from resume - Team collaboration experience - Leadership potential (if applicable) - Communication skills evidence 7. **Compensation Expectations** (💰) - Candidate's salary expectations vs. job offer - Negotiation room assessment - Total compensation considerations 8. **Advantages & Disadvantages** (✅ ⚠️) - List 3-5 key strengths of this candidate - List 2-4 potential concerns or gaps - Be objective and balanced 9. **Hiring Recommendation** (🎯) - Priority level: 🔥 High Priority / ⭐ Medium Priority / 💭 Consider - Recommended action with clear reasoning - Suggested interview focus areas - Onboarding considerations 10. **Interview Strategy** (📝) - Key areas to probe during interview - Technical assessment recommendations - Behavioral questions to ask - Red flags to watch for 11. **Retention & Growth Potential** (🚀) - Long-term fit assessment - Growth trajectory within the company - Retention risk factors - Development opportunities needed **After Individual Candidate Analysis:** **Comparative Summary Section:** 1. **Top 3 Recommendations** (🏆) - Rank top 3 candidates with medal emojis (🥇🥈🥉) - Brief rationale for each ranking 2. **Candidate Distribution** (📈) - Count of excellent matches (score > 0.85) - Count of good matches (score 0.75-0.85) - Count of moderate matches (score 0.65-0.75) 3. **Hiring Strategy Advice** (💡) - Which candidates to prioritize for interviews - Suggested interview panel composition - Timeline recommendations - Backup candidate strategy - Risk mitigation strategies 4. **Action Checklist** (🎯) - Immediate next steps (contact candidates, schedule interviews) - Preparation tasks (interview questions, evaluation criteria) - Budget/compensation considerations - Process setup (offer templates, onboarding plans) **Formatting Guidelines:** - Use emojis to make sections visually distinct - Use bullet points and numbered lists for clarity - Include visual separators (━━━) between major sections - Keep language professional and objective - Be specific and actionable in all recommendations - Balance honesty about gaps with recognition of potential #### Important Notes - **Always provide detailed analysis**: Don't just list candidates with scores. Hiring managers need actionable insights. - **Be objective about gaps**: Help identify areas where candidates might need support or training. - **Consider total value**: Match score is just one factor; potential, cultural fit, and long-term growth matter too. - **Prioritize actionability**: Every analysis should lead to clear hiring decisions and interview strategies. - **Personalize recommendations**: Reference specific details from the job requirements in your analysis. - **Think long-term**: Consider not just immediate fit, but retention and growth potential. --- ## API Configuration Default API endpoint: `https://api.jobclaw.ai` To use a different endpoint, modify the `apiUrl` parameter when calling the script. ## Error Handling If any operation fails: - Check if the API server is running - Verify all required fields are provided - Ensure the API endpoint is correct - For update/delete/matches: ensure a valid **jobId** is provided - Review the error message and guide the user accordingly ## Resources ### scripts/publish_job.py Python script supporting four actions (`publish`, `update`, `delete`, `matches`): - Creating new recruiter accounts (auto-created on publish) - Publishing and updating job postings - Soft-deleting job postings (mark INACTIVE) - Listing AI-matched candidates The script uses Python's built-in `urllib` library (no external dependencies required).

path: scripts/publish_job.py
content: #!/usr/bin/env python3
"""
Recruiter CLI: publish, update, delete job postings and list matched candidates.
"""
import sys
import json
from base import AuthenticatedClient, DEFAULT_API


JOB_FIELDS = ("title", "companyName", "requirement", "salary", "location", "jobType", "education", "experience")


def publish_job(api_url, data):
    """Publish a new job posting."""
    client = AuthenticatedClient(api_url, "RECRUITER")

    payload = {k: data[k] for k in JOB_FIELDS}
    payload["status"] = data.get("status", "ACTIVE")

    result = client.post("/jobs", payload)
    result["token"] = client.token_manager.get_token()
    return result


def update_job(api_url, data):
    """Update an existing job posting (partial update supported)."""
    client = AuthenticatedClient(api_url, "RECRUITER")

    job_id = data.get("jobId")
    if not job_id:
        return {"success": False, "error": "jobId is required for update"}

    payload = {k: data[k] for k in (*JOB_FIELDS, "status") if k in data}
    if not payload:
        return {"success": False, "error": "No fields to update"}

    result = client.put(f"/jobs/{job_id}", payload)
    result["token"] = client.token_manager.get_token()
    return result


def delete_job(api_url, data):
    """Soft-delete a job posting by setting status to INACTIVE."""
    client = AuthenticatedClient(api_url, "RECRUITER")

    job_id = data.get("jobId")
    if not job_id:
        return {"success": False, "error": "jobId is required for delete"}

    result = client.put(f"/jobs/{job_id}", {"status": "INACTIVE"})
    result["token"] = client.token_manager.get_token()
    return result


def list_matches(api_url, data):
    """List matched candidates for a specific job posting."""
    client = AuthenticatedClient(api_url, "RECRUITER")

    job_id = data.get("jobId")
    if not job_id:
        return {"success": False, "error": "jobId is required for listing matches"}

    result = client.get(f"/matches/job/{job_id}")
    result["token"] = client.token_manager.get_token()
    return result


# Action handlers
ACTIONS = {
    "publish": publish_job,
    "update": update_job,
    "delete": delete_job,
    "matches": list_matches,
}


if __name__ == "__main__":
    try:
        # Parse input
        data = None
        if len(sys.argv) > 1:
            data = json.loads(sys.argv[1])
        elif not sys.stdin.isatty():
            input_data = sys.stdin.read()
            if input_data.strip():
                data = json.loads(input_data)

        if not data:
            print(json.dumps({
                "success": False,
                "error": "Usage: publish_job.py <json> (or pipe json to stdin)"
            }))
            sys.exit(1)

        # Extract parameters
        api_url = data.pop("apiUrl", DEFAULT_API)
        action = data.pop("action", "publish")

        # Execute action
        fn = ACTIONS.get(action)
        if not fn:
            print(json.dumps({
                "success": False,
                "error": f"Unknown action: {action}. Use: {', '.join(ACTIONS)}"
            }))
            sys.exit(1)

        result = fn(api_url, data)
        print(json.dumps(result, ensure_ascii=False, indent=2))

    except Exception as e:
        print(json.dumps({"success": False, "error": str(e)}))
        sys.exit(1)


path: scripts/base.py
content: #!/usr/bin/env python3
"""
Base module for recruiter CLI scripts.
Handles token management, authentication, and HTTP requests.
"""
import os
import json
import urllib.request
import urllib.error

# Token storage location
TOKEN_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".token")

# Default API endpoint
DEFAULT_API = "https://api.jobclaw.ai"


class TokenManager:
    """Manages user authentication tokens."""

    def __init__(self, api_url=DEFAULT_API, user_type="RECRUITER"):
        self.api_url = api_url
        self.user_type = user_type
        self._token = None

    def get_token(self):
        """Get valid token (from cache, file, or create new)."""
        # 1. Return cached token if available
        if self._token:
            return self._token

        # 2. Try to load from file
        if os.path.exists(TOKEN_FILE):
            try:
                with open(TOKEN_FILE, 'r') as f:
                    saved_token = f.read().strip()

                # Verify token is still valid
                if self._verify_token(saved_token):
                    self._token = saved_token
                    return self._token
            except Exception:
                pass

        # 3. Create new token
        self._token = self._create_token()
        return self._token

    def _verify_token(self, token):
        """Verify if token is valid."""
        try:
            result = http_request(
                f"{self.api_url}/auth/verify",
                method="GET",
                token=token
            )
            return result.get("result", {}).get("valid", False)
        except Exception:
            return False

    def _create_token(self):
        """Create a new authentication token."""
        result = http_request(
            f"{self.api_url}/auth/token",
            method="POST",
            data={"userType": self.user_type}
        )

        if "result" not in result:
            raise Exception(f"Failed to create [REDACTED_SECRET]'error', 'Unknown error')}")

        new_token = result["result"]["token"]

        # Save token to file
        try:
            with open(TOKEN_FILE, 'w') as f:
                f.write(new_token)
        except Exception:
            pass  # Non-critical if we can't save

        return new_token

    def clear_token(self):
        """Clear cached token and file."""
        self._token = None
        if os.path.exists(TOKEN_FILE):
            try:
                os.remove(TOKEN_FILE)
            except Exception:
                pass


def http_request(url, method="GET", data=None, token=None):
    """
    Send HTTP request and return parsed JSON response.

    Args:
        url: Full URL to request
        method: HTTP method (GET, POST, PUT, DELETE)
        data: Request body data (will be JSON encoded)
        [REDACTED_SECRET] authentication token

    Returns:
        dict: Parsed JSON response

    Raises:
        Exception: If request fails
    """
    headers = {
        "Content-Type": "application/json",
        "User-Agent": "JobClaw-Skill-Script/2.0"
    }

    if [REDACTED_SECRET]"Authorization"] = f"Bearer {token}"

    body = json.dumps(data).encode("utf-8") if data else None
    req = urllib.request.Request(url, data=body, headers=headers, method=method)

    try:
        with urllib.request.urlopen(req) as resp:
            response_data = json.loads(resp.read().decode("utf-8"))

            # Check if response indicates success
            if not response_data.get("success", True):
                raise Exception(response_data.get("error", "Request failed"))

            return response_data

    except urllib.error.HTTPError as e:
        error_body = e.read().decode("utf-8")
        try:
            error_data = json.loads(error_body)
            error_msg = error_data.get("message", error_body)
        except Exception:
            error_msg = error_body

        raise Exception(f"HTTP {e.code}: {error_msg}")

    except urllib.error.URLError as e:
        raise Exception(f"Connection error: {e.reason}")


class AuthenticatedClient:
    """HTTP client with automatic token management."""

    def __init__(self, api_url=DEFAULT_API, user_type="RECRUITER"):
        self.api_url = api_url
        self.token_manager = TokenManager(api_url, user_type)

    def request(self, endpoint, method="GET", data=None, retry_auth=True):
        """
        Send authenticated request with automatic token refresh.

        Args:
            endpoint: API endpoint (e.g., "/jobs")
            method: HTTP method
            data: Request body data
            retry_auth: Whether to retry with new token if auth fails

        Returns:
            dict: Response data
        """
        url = f"{self.api_url}{endpoint}"
        [REDACTED_SECRET])

        try:
            return http_request(url, method, data, token)

        except Exception as e:
            error_msg = str(e)

            # If authentication failed and retry is enabled, get new token and retry
            if retry_auth and ("401" in error_msg or "Unauthorized" in error_msg):
                self.token_manager.clear_token()
                [REDACTED_SECRET])
                return http_request(url, method, data, token)

            raise

    def get(self, endpoint):
        """Send GET request."""
        return self.request(endpoint, "GET")

    def post(self, endpoint, data):
        """Send POST request."""
        return self.request(endpoint, "POST", data)

    def put(self, endpoint, data):
        """Send PUT request."""
        return self.request(endpoint, "PUT", data)

    def delete(self, endpoint):
        """Send DELETE request."""
        return self.request(endpoint, "DELETE")


def format_response(data, include_token=False):
    """
    Format response data for output.

    Args:
        data: Response data from API
        include_token: Whether to include token in output

    Returns:
        dict: Formatted response
    """
    response = {
        "success": data.get("success", True),
        "result": data.get("result")
    }

    if include_token:
        # This will be set by the calling script
        pass

    if "error" in data:
        response["error"] = data["error"]

    return response


path: scripts/get_profile.py
content: #!/usr/bin/env python3
"""
Recruiter profile and jobs viewer.
View your published jobs and matched candidates.
"""
import sys
import json
from base import AuthenticatedClient, DEFAULT_API


def get_jobs(api_url):
    """Get all published jobs by this recruiter."""
    client = AuthenticatedClient(api_url, "RECRUITER")
    result = client.get("/jobs/my-jobs")
    return result


def get_job_detail(api_url, job_id):
    """Get details of a specific job."""
    client = AuthenticatedClient(api_url, "RECRUITER")
    result = client.get(f"/jobs/{job_id}")
    return result


def get_job_matches(api_url, job_id):
    """Get matched candidates for a specific job."""
    client = AuthenticatedClient(api_url, "RECRUITER")
    result = client.get(f"/matches/job/{job_id}")
    return result


def get_all_matches(api_url):
    """Get all matches across all jobs."""
    client = AuthenticatedClient(api_url, "RECRUITER")

    # First get all jobs
    jobs_result = client.get("/jobs/my-jobs")

    if not jobs_result.get("success"):
        return jobs_result

    jobs = jobs_result.get("result", [])

    # Get matches for each job
    all_matches = {}
    for job in jobs:
        job_id = job.get("id")
        try:
            matches_result = client.get(f"/matches/job/{job_id}")
            all_matches[job_id] = {
                "job": job,
                "matches": matches_result.get("result", [])
            }
        except Exception as e:
            all_matches[job_id] = {
                "job": job,
                "matches": [],
                "error": str(e)
            }

    return {
        "success": True,
        "result": all_matches
    }


def get_full_info(api_url, job_id=None):
    """Get complete information: jobs + matches."""
    client = AuthenticatedClient(api_url, "RECRUITER")

    if job_id:
        # Get specific job and its matches
        job_result = client.get(f"/jobs/{job_id}")
        matches_result = client.get(f"/matches/job/{job_id}")

        return {
            "success": True,
            "result": {
                "job": job_result.get("result"),
                "matches": matches_result.get("result")
            }
        }
    else:
        # Get all jobs and all matches
        return get_all_matches(api_url)


# Action handlers
ACTIONS = {
    "jobs": get_jobs,
    "job": get_job_detail,
    "matches": get_job_matches,
    "all-matches": get_all_matches,
    "full": get_full_info,
}


if __name__ == "__main__":
    try:
        # Parse input
        data = None
        if len(sys.argv) > 1:
            data = json.loads(sys.argv[1])
        elif not sys.stdin.isatty():
            input_data = sys.stdin.read()
            if input_data.strip():
                data = json.loads(input_data)

        if not data:
            print(json.dumps({
                "success": False,
                "error": "Usage: get_profile.py <json> (or pipe json to stdin)\n"
                         "Actions: jobs, job (requires jobId), matches (requires jobId), all-matches, full"
            }))
            sys.exit(1)

        # Extract parameters
        api_url = data.pop("apiUrl", DEFAULT_API)
        action = data.pop("action", "full")
        job_id = data.get("jobId")

        # Execute action
        fn = ACTIONS.get(action)
        if not fn:
            print(json.dumps({
                "success": False,
                "error": f"Unknown action: {action}. Use: {', '.join(ACTIONS)}"
            }))
            sys.exit(1)

        # Call function with appropriate parameters
        if action in ["job", "matches"]:
            if not job_id:
                print(json.dumps({
                    "success": False,
                    "error": f"Action '{action}' requires jobId parameter"
                }))
                sys.exit(1)
            result = fn(api_url, job_id)
        elif action == "full" and job_id:
            result = fn(api_url, job_id)
        else:
            result = fn(api_url)

        print(json.dumps(result, ensure_ascii=False, indent=2))

    except Exception as e:
        print(json.dumps({"success": False, "error": str(e)}))
        sys.exit(1)


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