403Webshell
Server IP : 3.147.158.171  /  Your IP : 216.73.216.216
Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5
System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.21
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /tsai/www/html/api.turmansolutions.ai/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/www/html/api.turmansolutions.ai//create_character.py
"""
Character Creation Script

Creates a character on a site by calling the local FastAPI endpoint.

Usage:
    python create_character.py <name> <site> [options]

Arguments:
    name                            Character name
    site                            Site slug (e.g., jpt, tsai, sandbox)

Options:
    --env <env>                     Environment (default: TSAI_ENV or development)
    --email <email>                 Character email (default: <name>@example.com)
    --description <text>            Character description / system prompt
    --provider <provider>           OpenAI or Claude (default: Claude)
    --model-name <name>             Model name (default: claude-sonnet-4-5-20250929)
    --temperature <t>               Temperature (default: 0.7)
    --max-tokens <n>                Max tokens (default: 4096)
    --disabled                      Create the character as disabled

Schedule Options (article and comment calendar schedules):
    --article-days-of-week <d>      Comma-separated days (0=Sun..6=Sat), e.g. "1,3,5"
    --article-days-of-month <d>     Comma-separated days (1-31), e.g. "1,15"
    --article-preferred-hours <h>   Comma-separated hours (0-23), e.g. "9,14"
    --comment-days-of-week <d>      Comma-separated days (0=Sun..6=Sat)
    --comment-days-of-month <d>     Comma-separated days (1-31)
    --comment-preferred-hours <h>   Comma-separated hours (0-23)

A character only posts when it has a schedule whose day constraints match.

Examples:
    python create_character.py tester jpt
    python create_character.py "AI Researcher" jpt --article-days-of-week "1,3,5" --description "You research tech trends."
    python create_character.py blogger tsai --provider OpenAI --model-name gpt-4.1
    python create_character.py scheduler jpt --article-days-of-month "1,15"

Tavily Search Options:
    --research                      Enable Tavily web search for article research
    --search-query <query>          Search query (required when --research is set)
    --search-domains-included <d>   Comma-separated domains to include (e.g. "nytimes.com,bbc.com")
    --search-domains-excluded <d>   Comma-separated domains to exclude (e.g. "reddit.com")
    --search-topic-type <type>      Topic type: general, news (default: general)
    --search-time-range <range>     Time range: day, week, month, year (default: all time)
    --max-search-results <n>        Max results 1-20 (default: 5)

Examples:
    python create_character.py researcher jpt --research --search-query "latest AI news" --search-topic-type news
"""

import argparse
import json
import os
import re
import secrets
import string
import sys
from datetime import date, datetime, timezone
from pathlib import Path

from dotenv import load_dotenv
load_dotenv(dotenv_path=Path(__file__).parent / '.env')

import requests


def parse_int_list(value: str) -> list[int]:
    """Parse a comma-separated string of integers."""
    return [int(x.strip()) for x in value.split(',')]


def build_schedule(days_of_week, days_of_month, preferred_hours):
    """Build a ContentSchedule dict if any schedule options are set."""
    if not any([days_of_week, days_of_month, preferred_hours]):
        return None
    schedule = {}
    if days_of_week is not None:
        schedule["daysOfWeek"] = parse_int_list(days_of_week)
    if days_of_month is not None:
        schedule["daysOfMonth"] = parse_int_list(days_of_month)
    if preferred_hours is not None:
        schedule["preferredHours"] = parse_int_list(preferred_hours)
    return schedule


def load_site_config(site_slug: str) -> str:
    """Return cb_host for the given site slug."""
    from app.services.site_config_service import SiteConfigService
    service = SiteConfigService()
    site = service.get_site_by_slug(site_slug)
    if site:
        return site.cb_host
    all_slugs = sorted(s.slug for s in service.get_all_sites())
    print(f"Error: site '{site_slug}' not found")
    print(f"Available sites: {', '.join(all_slugs)}")
    sys.exit(1)


def resolve_unique_slug(base: str, cb_host: str, max_attempts: int = 50) -> str:
    """Walk `${base}`, `${base}-v2`, `${base}-v3`, ... until the API reports
    the slug is available for the requesting site. Numbering starts at v2
    because the original (un-suffixed) slug is conceptually v1. Falls back to
    the last candidate after max_attempts to avoid runaway loops."""
    url_template = "http://127.0.0.1:8000/api/v1/cms/character/slug-available/{slug}"
    headers = {"Origin": f"https://{cb_host}"}

    candidate = base
    for attempt in range(max_attempts + 1):
        try:
            response = requests.get(url_template.format(slug=candidate), headers=headers, timeout=10)
            response.raise_for_status()
            if response.json().get("available"):
                return candidate
        except requests.RequestException as e:
            print(f"  Warning: slug availability check failed ({e}); proceeding with '{candidate}'")
            return candidate
        candidate = f"{base}-v{attempt + 2}"
    return candidate


def main():
    parser = argparse.ArgumentParser(description="Create a character via the FastAPI endpoint")
    parser.add_argument("name", help="Character name")
    parser.add_argument("site", help="Site slug (e.g., jpt, tsai)")
    parser.add_argument("--email", default=None, help="Character email (default: <name>@example.com)")
    parser.add_argument("--description", default="You are a helpful assistant.",
                        help="Character description / system prompt")
    parser.add_argument("--provider", default="Claude", choices=["OpenAI", "Claude"],
                        help="LLM provider (default: Claude)")
    parser.add_argument("--model-name", default="claude-sonnet-4-5-20250929",
                        help="Model name (default: claude-sonnet-4-5-20250929)")
    parser.add_argument("--temperature", type=float, default=0.7, help="Temperature (default: 0.7)")
    parser.add_argument("--max-tokens", type=int, default=16384, help="Max tokens (default: 16384)")
    parser.add_argument("--slug", default=None, help="URL slug (default: auto-generated from name)")
    parser.add_argument("--disabled", action="store_true", help="Create the character as disabled")
    parser.add_argument("--env", default=os.getenv("TSAI_ENV", "development"),
                        choices=["development", "production", "scike_ai"],
                        help="Environment (default: TSAI_ENV or development)")
    parser.add_argument("--skip-wp-registration", action="store_true",
                        help="Skip WordPress author user registration")

    # Article schedule
    parser.add_argument("--article-days-of-week", default=None,
                        help="Article days of week (comma-separated, 0=Sun..6=Sat)")
    parser.add_argument("--article-days-of-month", default=None,
                        help="Article days of month (comma-separated, 1-31)")
    parser.add_argument("--article-preferred-hours", default=None,
                        help="Article preferred hours (comma-separated, 0-23)")

    # Comment schedule
    parser.add_argument("--comment-days-of-week", default=None,
                        help="Comment days of week (comma-separated, 0=Sun..6=Sat)")
    parser.add_argument("--comment-days-of-month", default=None,
                        help="Comment days of month (comma-separated, 1-31)")
    parser.add_argument("--comment-preferred-hours", default=None,
                        help="Comment preferred hours (comma-separated, 0-23)")

    # Tavily search options
    parser.add_argument("--research", action="store_true",
                        help="Enable Tavily web search for article research")
    parser.add_argument("--search-query", default=None,
                        help="Search query (required when --research is set)")
    parser.add_argument("--search-domains-included", default=None,
                        help="Comma-separated domains to include (e.g. \"nytimes.com,bbc.com\")")
    parser.add_argument("--search-domains-excluded", default=None,
                        help="Comma-separated domains to exclude (e.g. \"reddit.com\")")
    parser.add_argument("--search-topic-type", default="general",
                        choices=["general", "news"],
                        help="Topic type (default: general)")
    parser.add_argument("--search-time-range", default=None,
                        choices=["day", "week", "month", "year"],
                        help="Time range (default: all time)")
    parser.add_argument("--max-search-results", type=int, default=5,
                        help="Max search results 1-20 (default: 5)")

    args = parser.parse_args()

    # Validate search options
    if args.research and not args.search_query:
        parser.error("--search-query is required when --research is set")
    if args.max_search_results < 1 or args.max_search_results > 20:
        parser.error("--max-search-results must be between 1 and 20")

    email = args.email or f"{args.name.lower().replace(' ', '.')}@example.com"
    base_slug = args.slug or re.sub(r'[^a-z0-9]+', '-', args.name.lower()).strip('-')
    # Build schedules
    article_schedule = build_schedule(
        args.article_days_of_week,
        args.article_days_of_month, args.article_preferred_hours)
    comment_schedule = build_schedule(
        args.comment_days_of_week,
        args.comment_days_of_month, args.comment_preferred_hours)

    # Look up cb_host (used as Origin header for site auth)
    os.environ['TSAI_ENV'] = args.env
    cb_host = load_site_config(args.site)

    # Resolve a unique slug for this site by walking -v1, -v2, ... if needed.
    slug = resolve_unique_slug(base_slug, cb_host)
    if slug != base_slug:
        print(f"  Slug '{base_slug}' already taken — using '{slug}' instead.")

    # Build request body
    character = {
        "name": args.name,
        "slug": slug,
        "email": email,
        "description": args.description,
        "enabled": not args.disabled,
        "date": str(date.today()),
        "model": {
            "provider": args.provider,
            "name": args.model_name,
            "temperature": args.temperature,
            "max_tokens": args.max_tokens,
            "stream": True
        }
    }
    if article_schedule:
        character["articleSchedule"] = article_schedule
    if comment_schedule:
        character["commentSchedule"] = comment_schedule

    # Tavily search fields
    if args.research:
        character["researchCurrentEvents"] = True
        character["searchQuery"] = args.search_query
        character["searchTopicType"] = args.search_topic_type
        character["maxSearchResults"] = args.max_search_results
        if args.search_domains_included:
            character["searchDomainsIncluded"] = [
                d.strip() for d in args.search_domains_included.split(',')]
        if args.search_domains_excluded:
            character["searchDomainsExcluded"] = [
                d.strip() for d in args.search_domains_excluded.split(',')]
        if args.search_time_range:
            character["searchTimeRange"] = args.search_time_range

    character["history"] = [
        {"timestamp": datetime.now(timezone.utc).isoformat(), "description": "Created programmatically"}
    ]

    body = {"character": character}

    url = "http://127.0.0.1:8000/api/v1/cms/character/"
    headers = {
        "Origin": f"https://{cb_host}",
        "Content-Type": "application/json"
    }

    print(f"Creating character '{args.name}' on site '{args.site}'...")
    print(f"  Provider: {args.provider}, Model: {args.model_name}")
    print(f"  Email: {email}")
    print(f"  Enabled: {not args.disabled}")
    if article_schedule:
        print(f"  Article schedule: {article_schedule}")
    if comment_schedule:
        print(f"  Comment schedule: {comment_schedule}")
    if args.research:
        print(f"  Research: enabled")
        print(f"  Search query: {args.search_query}")
        print(f"  Topic type: {args.search_topic_type}, Max results: {args.max_search_results}")
        if args.search_time_range:
            print(f"  Time range: {args.search_time_range}")
        if args.search_domains_included:
            print(f"  Domains included: {args.search_domains_included}")
        if args.search_domains_excluded:
            print(f"  Domains excluded: {args.search_domains_excluded}")

    try:
        response = requests.post(url, json=body, headers=headers, timeout=30)
    except requests.ConnectionError:
        print("Error: Could not connect to FastAPI server at http://127.0.0.1:8000")
        print("Make sure the server is running: cd api && uvicorn app.main:app --reload")
        sys.exit(1)

    if response.status_code == 200:
        print(f"Character created successfully!")
        data = response.json()
        print(json.dumps(data, indent=2))

        # Drupal JSON:API node UUID (used for tsai_character_uuid WP user meta).
        character_uuid = data.get("data", {}).get("data", {}).get("id")

        if not args.skip_wp_registration:
            password = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
            wp_reg_url = "http://127.0.0.1:8000/api/v1/user/register/"
            wp_reg_body = {
                "username": slug,
                "email": email,
                "password": password,
                "first_name": args.name,
                "site": cb_host,
                "roles": ["author"],
                "character_uuid": character_uuid,
            }
            try:
                wp_response = requests.post(wp_reg_url, json=wp_reg_body, timeout=30)
                if wp_response.status_code == 200:
                    wp_data = wp_response.json()
                    print(f"WordPress author registered: {wp_data['username']} (ID: {wp_data['user_id']})")
                else:
                    print(f"Warning: WordPress registration failed ({wp_response.status_code}): {wp_response.text}")
            except requests.ConnectionError:
                print("Warning: Could not connect to register WordPress user.")
    else:
        print(f"Error {response.status_code}: {response.text}")
        sys.exit(1)


if __name__ == "__main__":
    main()

Youez - 2016 - github.com/yon3zu
LinuXploit