diff --git a/skills/deckofcards/__init__.py b/skills/deckofcards/__init__.py new file mode 100644 index 00000000..dcdefbfb --- /dev/null +++ b/skills/deckofcards/__init__.py @@ -0,0 +1,66 @@ +"""Deck of Cards skills.""" + +from typing import TypedDict + +from abstracts.skill import SkillStoreABC +from skills.base import SkillConfig, SkillState +from skills.deckofcards.add_to_pile import AddToPile +from skills.deckofcards.base import DeckOfCardsBaseSkill +from skills.deckofcards.draw_cards import DrawCards +from skills.deckofcards.draw_from_pile import DrawFromPile +from skills.deckofcards.list_piles import ListPiles +from skills.deckofcards.shuffle_deck import ShuffleDeck + +# Cache skills at the system level, because they are stateless +_cache: dict[str, DeckOfCardsBaseSkill] = {} + + +class SkillStates(TypedDict): + shuffle_deck: SkillState + + +class Config(SkillConfig): + """Configuration for Deck of Cards skills.""" + + states: SkillStates + + +async def get_skills( + config: "Config", + is_private: bool, + store: SkillStoreABC, + **_, +) -> list[DeckOfCardsBaseSkill]: + """Get all Deck of Cards skills.""" + available_skills = [] + + # Include skills based on their state + for skill_name, state in config["states"].items(): + if state == "disabled": + continue + elif state == "public" or (state == "private" and is_private): + available_skills.append(skill_name) + + # Get each skill using the cached getter + return [get_deckofcards_skill(name, store) for name in available_skills] + + +def get_deckofcards_skill( + name: str, + store: SkillStoreABC, +) -> DeckOfCardsBaseSkill: + """Get a Deck of Cards skill by name.""" + if name not in _cache: + if name == "shuffle_deck": + _cache[name] = ShuffleDeck(skill_store=store) + elif name == "draw_cards": + _cache[name] = DrawCards(skill_store=store) + elif name == "add_to_pile": + _cache[name] = AddToPile(skill_store=store) + elif name == "draw_from_pile": + _cache[name] = DrawFromPile(skill_store=store) + elif name == "list_piles": + _cache[name] = ListPiles(skill_store=store) + else: + raise ValueError(f"Unknown Deck of Cards skill: {name}") + return _cache[name] diff --git a/skills/deckofcards/add_to_pile.py b/skills/deckofcards/add_to_pile.py new file mode 100644 index 00000000..303dd967 --- /dev/null +++ b/skills/deckofcards/add_to_pile.py @@ -0,0 +1,58 @@ +from typing import List + +import httpx + +from .base import DeckOfCardsBaseSkill + + +class AddToPile(DeckOfCardsBaseSkill): + """Skill to add cards to a named pile.""" + + name = "add_to_pile" + description = "Add specified cards to a named pile in the deck" + + async def _arun(self, deck_id: str, pile_name: str, cards: List[str]) -> str: + """ + Add cards to a named pile. + + Args: + deck_id: ID of the deck + pile_name: Name of the pile to add cards to + cards: List of card codes to add (e.g., ["AS", "2S"]) + + Returns: + str: Formatted response containing pile information + """ + try: + # Convert list of cards to comma-separated string + cards_str = ",".join(cards) + + async with httpx.AsyncClient() as client: + response = await client.get( + f"https://deckofcardsapi.com/api/deck/{deck_id}/pile/{pile_name}/add/", + params={"cards": cards_str}, + ) + response.raise_for_status() + data = response.json() + + if not data["success"]: + return "Error: Failed to add cards to pile" + + result = [ + "Cards Added to Pile Successfully", + f"Deck ID: {data['deck_id']}", + f"Cards Remaining in Deck: {data['remaining']}", + "", + "Pile Status:", + ] + + # Add information about each pile + for pile_name, pile_info in data["piles"].items(): + result.append(f"- {pile_name}: {pile_info['remaining']} cards") + + return "\n".join(result) + + except httpx.HTTPError as e: + return f"Error adding cards to pile: {str(e)}" + except Exception as e: + return f"Unexpected error: {str(e)}" diff --git a/skills/deckofcards/base.py b/skills/deckofcards/base.py new file mode 100644 index 00000000..eebe4ea4 --- /dev/null +++ b/skills/deckofcards/base.py @@ -0,0 +1,21 @@ +from typing import Type + +from pydantic import BaseModel, Field + +from abstracts.skill import SkillStoreABC +from skills.base import IntentKitSkill + + +class DeckOfCardsBaseSkill(IntentKitSkill): + """Base class for Deck of Cards API tools.""" + + name: str = Field(description="The name of the tool") + description: str = Field(description="A description of what the tool does") + args_schema: Type[BaseModel] + skill_store: SkillStoreABC = Field( + description="The skill store for persisting data" + ) + + @property + def category(self) -> str: + return "deckofcards" diff --git a/skills/deckofcards/deckofcards.png b/skills/deckofcards/deckofcards.png new file mode 100644 index 00000000..454ef5a3 Binary files /dev/null and b/skills/deckofcards/deckofcards.png differ diff --git a/skills/deckofcards/draw_cards.py b/skills/deckofcards/draw_cards.py new file mode 100644 index 00000000..61e9ebff --- /dev/null +++ b/skills/deckofcards/draw_cards.py @@ -0,0 +1,57 @@ +import httpx + +from .base import DeckOfCardsBaseSkill + + +class DrawCards(DeckOfCardsBaseSkill): + """Skill to draw cards from a deck.""" + + name = "draw_cards" + description = "Draw a specified number of cards from a deck" + + async def _arun(self, deck_id: str, count: int = 1) -> str: + """ + Draw cards from a deck. + + Args: + deck_id: ID of the deck to draw from + count: Number of cards to draw (default: 1) + params: Additional parameters (unused) + + Returns: + str: Formatted response containing drawn cards information + """ + try: + async with httpx.AsyncClient() as client: + response = await client.get( + f"https://deckofcardsapi.com/api/deck/{deck_id}/draw/", + params={"count": count}, + ) + response.raise_for_status() + data = response.json() + + if not data["success"]: + return "Error: Failed to draw cards" + + result = [ + "Cards Drawn Successfully", + f"Deck ID: {data['deck_id']}", + f"Cards Remaining: {data['remaining']}", + "", + "Drawn Cards:", + ] + + for card in data["cards"]: + result.extend( + [ + f"- {card['value']} of {card['suit']}", + f" Image: {card['image']}", + ] + ) + + return "\n".join(result) + + except httpx.HTTPError as e: + return f"Error drawing cards: {str(e)}" + except Exception as e: + return f"Unexpected error: {str(e)}" diff --git a/skills/deckofcards/draw_from_pile.py b/skills/deckofcards/draw_from_pile.py new file mode 100644 index 00000000..7539ed5f --- /dev/null +++ b/skills/deckofcards/draw_from_pile.py @@ -0,0 +1,69 @@ +from typing import List + +import httpx + +from .base import DeckOfCardsBaseSkill + + +class DrawFromPile(DeckOfCardsBaseSkill): + """Skill to draw cards from a named pile.""" + + name = "draw_from_pile" + description = "Draw specified cards from a named pile in the deck" + + async def _arun(self, deck_id: str, pile_name: str, cards: List[str]) -> str: + """ + Draw cards from a named pile. + + Args: + deck_id: ID of the deck + pile_name: Name of the pile to draw from + cards: List of card codes to draw (e.g., ["AS", "2S"]) + + Returns: + str: Formatted response containing drawn cards and pile information + """ + try: + # Convert list of cards to comma-separated string + cards_str = ",".join(cards) + + async with httpx.AsyncClient() as client: + response = await client.get( + f"https://deckofcardsapi.com/api/deck/{deck_id}/pile/{pile_name}/draw/", + params={"cards": cards_str}, + ) + response.raise_for_status() + data = response.json() + + if not data["success"]: + return "Error: Failed to draw cards from pile" + + result = [ + "Cards Drawn from Pile Successfully", + f"Deck ID: {data['deck_id']}", + f"Cards Remaining in Deck: {data['remaining']}", + "", + "Pile Status:", + ] + + # Add information about each pile + for pile_name, pile_info in data["piles"].items(): + result.append(f"- {pile_name}: {pile_info['remaining']} cards") + + # Add information about drawn cards + if "cards" in data: + result.extend(["", "Drawn Cards:"]) + for card in data["cards"]: + result.extend( + [ + f"- {card['value']} of {card['suit']}", + f" Image: {card['image']}", + ] + ) + + return "\n".join(result) + + except httpx.HTTPError as e: + return f"Error drawing cards from pile: {str(e)}" + except Exception as e: + return f"Unexpected error: {str(e)}" diff --git a/skills/deckofcards/list_piles.py b/skills/deckofcards/list_piles.py new file mode 100644 index 00000000..054bbae1 --- /dev/null +++ b/skills/deckofcards/list_piles.py @@ -0,0 +1,62 @@ +import httpx + +from .base import DeckOfCardsBaseSkill + + +class ListPiles(DeckOfCardsBaseSkill): + """Skill to list all piles and their contents in a deck.""" + + name = "list_piles" + description = "View existing piles and their contents in the deck" + + async def _arun(self, deck_id: str) -> str: + """ + List all piles and their contents. + + Args: + deck_id: ID of the deck + + Returns: + str: Formatted response containing pile information and contents + """ + try: + async with httpx.AsyncClient() as client: + response = await client.get( + f"https://deckofcardsapi.com/api/deck/{deck_id}/pile/list/" + ) + response.raise_for_status() + data = response.json() + + if not data["success"]: + return "Error: Failed to list piles" + + result = [ + "Piles Listed Successfully", + f"Deck ID: {data['deck_id']}", + f"Cards Remaining in Deck: {data['remaining']}", + "", + "Pile Contents:", + ] + + # Add information about each pile + for pile_name, pile_info in data["piles"].items(): + result.append(f"\n{pile_name}:") + result.append(f"- Cards Remaining: {pile_info['remaining']}") + + # Add card details if available + if "cards" in pile_info: + result.append("- Cards:") + for card in pile_info["cards"]: + result.extend( + [ + f" * {card['value']} of {card['suit']}", + f" Image: {card['image']}", + ] + ) + + return "\n".join(result) + + except httpx.HTTPError as e: + return f"Error listing piles: {str(e)}" + except Exception as e: + return f"Unexpected error: {str(e)}" diff --git a/skills/deckofcards/schema.json b/skills/deckofcards/schema.json new file mode 100644 index 00000000..9b2155a0 --- /dev/null +++ b/skills/deckofcards/schema.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Deck of Cards", + "description": "Configuration schema for Deck of Cards skills", + "x-icon": "https://ai.service.crestal.dev/skills/deckofcards/deckofcards.png", + "x-tags": [ + "Games", + "Cards", + "Entertainment" + ], + "properties": { + "enabled": { + "type": "boolean", + "title": "Enabled", + "description": "Whether Deck of Cards skills are enabled", + "default": false + }, + "states": { + "type": "object", + "properties": { + "shuffle_deck": { + "type": "string", + "title": "Shuffle Deck", + "enum": [ + "disabled", + "public", + "private" + ], + "description": "Initialize and shuffle a new deck of cards" + }, + "draw_cards": { + "type": "string", + "title": "Draw Cards", + "enum": [ + "disabled", + "public", + "private" + ], + "description": "Draw a specified number of cards from a deck" + }, + "draw_from_pile": { + "type": "string", + "title": "Draw From Pile", + "enum": [ + "disabled", + "public", + "private" + ], + "description": "Draw specified cards from a named pile" + }, + "list_piles": { + "type": "string", + "title": "List Piles", + "enum": [ + "disabled", + "public", + "private" + ], + "description": "View existing piles and their contents in a deck" + }, + "add_to_pile": { + "type": "string", + "title": "Add to Pile", + "enum": [ + "disabled", + "public", + "private" + ], + "description": "Add specified cards to a named pile" + } + }, + "description": "States for each Deck of Cards skill (disabled, public, or private)" + } + }, + "required": [ + "states", + "enabled" + ], + "additionalProperties": true +} \ No newline at end of file diff --git a/skills/deckofcards/shuffle_deck.py b/skills/deckofcards/shuffle_deck.py new file mode 100644 index 00000000..f3b2aa51 --- /dev/null +++ b/skills/deckofcards/shuffle_deck.py @@ -0,0 +1,46 @@ +import httpx + +from .base import DeckOfCardsBaseSkill + + +class ShuffleDeck(DeckOfCardsBaseSkill): + """Skill to shuffle a new deck of cards.""" + + name = "shuffle_deck" + description = "Initialize and shuffle a new deck of cards" + + async def _arun(self, deck_count: int = 1) -> str: + """ + Shuffle a new deck of cards. + + Args: + deck_count: Number of decks to shuffle (default: 1) + params: Additional parameters (unused) + + Returns: + str: Formatted response containing deck information + """ + try: + async with httpx.AsyncClient() as client: + response = await client.get( + "https://deckofcardsapi.com/api/deck/new/shuffle/", + params={"deck_count": deck_count}, + ) + response.raise_for_status() + data = response.json() + + result = [ + "Deck Shuffled Successfully", + f"Deck ID: {data['deck_id']}", + f"Cards Remaining: {data['remaining']}", + f"Status: {'Shuffled' if data['shuffled'] else 'Not Shuffled'}", + "", + "You can now use this deck for drawing cards or other operations.", + ] + + return "\n".join(result) + + except httpx.HTTPError as e: + return f"Error shuffling deck: {str(e)}" + except Exception as e: + return f"Unexpected error: {str(e)}"