diff --git a/libs/arcade-cli/arcade_cli/new.py b/libs/arcade-cli/arcade_cli/new.py index de9e2ca2..66b89695 100644 --- a/libs/arcade-cli/arcade_cli/new.py +++ b/libs/arcade-cli/arcade_cli/new.py @@ -3,9 +3,7 @@ import shutil from datetime import datetime from importlib.metadata import version as get_version from pathlib import Path -from typing import Optional -import typer from jinja2 import Environment, FileSystemLoader, select_autoescape from arcade_cli.console import console @@ -20,48 +18,10 @@ except Exception as e: ARCADE_MCP_MIN_VERSION = "1.10.0" # Default version if unable to fetch ARCADE_MCP_MAX_VERSION = "2.0.0" -ARCADE_TDK_MIN_VERSION = "3.6.0" -ARCADE_TDK_MAX_VERSION = "4.0.0" -ARCADE_SERVE_MIN_VERSION = "3.1.5" -ARCADE_SERVE_MAX_VERSION = "4.0.0" ARCADE_MCP_SERVER_MIN_VERSION = "1.17.0" ARCADE_MCP_SERVER_MAX_VERSION = "2.0.0" -def ask_question(question: str, default: Optional[str] = None) -> str: - """ - Ask a question via input() and return the answer. - """ - answer = typer.prompt(question, default=default, show_default=False) - if not answer and default: - return default - return str(answer) - - -def ask_yes_no_question(question: str, default: bool = True) -> bool: - """ - Ask a yes/no question via input() and return the bool answer. - """ - default_str = "Y/n" if default else "y/N" - answer = typer.prompt( - f"{question} ({default_str})", default="y" if default else "n", show_default=False - ) - return answer.lower() in [ - "y", - "y/", - "yes", - "true", - "1", - "ye", - "yes", - "yeah", - "yep", - "sure", - "ok", - "yup", - ] - - def render_template(env: Environment, template_string: str, context: dict) -> str: """Render a template string with the given variables.""" template = env.from_string(template_string) @@ -73,9 +33,7 @@ def write_template(path: Path, content: str) -> None: path.write_text(content, encoding="utf-8") -def create_ignore_pattern( - include_evals: bool, is_community_or_official_toolkit: bool -) -> re.Pattern[str]: +def create_ignore_pattern(include_evals: bool) -> re.Pattern[str]: """Create an ignore pattern based on user preferences.""" patterns = [ "__pycache__", @@ -96,11 +54,6 @@ def create_ignore_pattern( if not include_evals: patterns.append("evals") - if not is_community_or_official_toolkit: - patterns.extend([".ruff.toml", ".pre-commit-config.yaml", "LICENSE"]) - else: - patterns.extend(["README.md"]) - return re.compile(f"({'|'.join(patterns)})$") @@ -166,47 +119,21 @@ def create_new_toolkit(output_directory: str, toolkit_name: str) -> None: ) exit(1) - toolkit_description = ask_question("Describe what your server will do (optional)", default="") - toolkit_author_name = ask_question("Your GitHub username (optional)", default="") - while True: - toolkit_author_email = ask_question("Your email (optional)", default="") - if toolkit_author_email == "" or re.match(r"[^@ ]+@[^@ ]+\.[^@ ]+", toolkit_author_email): - break - console.print( - "[red]Invalid email format. Please enter a valid email address or leave it empty.[/red]" - ) - include_evals = ask_yes_no_question( - "Do you want an evals directory created for you?", default=True - ) - - cwd = Path.cwd() - # TODO: this detection mechanism works only for people that didn't change the - # name of the repo, a better detection method is required here - is_community_toolkit = False - if cwd.name == "toolkits" and cwd.parent.name == "arcade-mcp": - prompt = ( - "Is your server a community contribution (to be merged into " - "\x1b]8;;https://github.com/ArcadeAI/arcade-mcp\x1b\\ArcadeAI/arcade-mcp\x1b]8;;\x1b\\ repo)?" - ) - is_community_toolkit = ask_yes_no_question(prompt, default=True) - - is_official_toolkit = cwd.name == "toolkits" and cwd.parent.name == "tools" + toolkit_name_title = toolkit_name.replace("_", " ").title() + toolkit_name_hyphenated = toolkit_name.replace("_", "-") + toolkit_description = f"Arcade.dev tools for interacting with {toolkit_name_title}" context = { - "package_name": "arcade_" + toolkit_name if is_community_toolkit else toolkit_name, + "package_name": "arcade_" + toolkit_name, "toolkit_name": toolkit_name, + "toolkit_name_title": toolkit_name_title, + "toolkit_name_hyphenated": toolkit_name_hyphenated, "toolkit_description": toolkit_description, - "toolkit_author_name": toolkit_author_name, - "toolkit_author_email": toolkit_author_email, - "arcade_tdk_min_version": ARCADE_TDK_MIN_VERSION, - "arcade_tdk_max_version": ARCADE_TDK_MAX_VERSION, - "arcade_serve_min_version": ARCADE_SERVE_MIN_VERSION, - "arcade_serve_max_version": ARCADE_SERVE_MAX_VERSION, + "arcade_mcp_server_min_version": ARCADE_MCP_SERVER_MIN_VERSION, + "arcade_mcp_server_max_version": ARCADE_MCP_SERVER_MAX_VERSION, "arcade_mcp_min_version": ARCADE_MCP_MIN_VERSION, "arcade_mcp_max_version": ARCADE_MCP_MAX_VERSION, "creation_year": datetime.now().year, - "is_community_toolkit": is_community_toolkit, - "is_official_toolkit": is_official_toolkit, } template_directory = get_full_template_directory() / "{{ toolkit_name }}" @@ -214,12 +141,10 @@ def create_new_toolkit(output_directory: str, toolkit_name: str) -> None: env = Environment( loader=FileSystemLoader(str(template_directory)), autoescape=select_autoescape(["html", "xml"]), + keep_trailing_newline=True, ) - # Create dynamic ignore pattern based on user preferences - ignore_pattern = create_ignore_pattern( - include_evals, is_community_toolkit or is_official_toolkit - ) + ignore_pattern = create_ignore_pattern(include_evals=True) try: create_package(env, template_directory, toolkit_directory, context, ignore_pattern) @@ -228,23 +153,16 @@ def create_new_toolkit(output_directory: str, toolkit_name: str) -> None: ) console.print("\nNext steps:", style="bold") console.print(f" 1. cd {toolkit_directory / toolkit_name}") + console.print(" 2. make install") + console.print(" 3. make dev # serve with MCP and worker endpoints") + console.print(" 4. make test # run tests") + console.print(" 5. make lint # run linting") console.print("") - console.print(" 2. Run the server (choose one transport):", style="dim") - console.print(" - stdio: uv run server.py") - console.print(" - http: uv run server.py --transport http --port 8000") - console.print("") - create_deployment(toolkit_directory, toolkit_name) except Exception: remove_toolkit(toolkit_directory, toolkit_name) raise -def create_deployment(toolkit_directory: Path, toolkit_name: str) -> None: - # No longer create worker.toml for MCP servers - # The server.py file handles all configuration - pass - - def create_new_toolkit_minimal(output_directory: str, toolkit_name: str) -> None: """Create a new toolkit from a template with user input.""" toolkit_directory = Path(output_directory) @@ -274,9 +192,10 @@ def create_new_toolkit_minimal(output_directory: str, toolkit_name: str) -> None env = Environment( loader=FileSystemLoader(str(template_directory)), autoescape=select_autoescape(["html", "xml"]), + keep_trailing_newline=True, ) - ignore_pattern = create_ignore_pattern(False, False) + ignore_pattern = create_ignore_pattern(False) try: create_package(env, template_directory, toolkit_directory, context, ignore_pattern) diff --git a/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/.ruff.toml b/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/.ruff.toml index 9519fe6c..f1aed90f 100644 --- a/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/.ruff.toml +++ b/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/.ruff.toml @@ -37,7 +37,9 @@ select = [ ] [lint.per-file-ignores] -"**/tests/*" = ["S101"] +"*" = ["TRY003", "B904"] +"**/tests/*" = ["S101", "E501"] +"**/evals/*" = ["S101", "E501"] [format] preview = true diff --git a/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/LICENSE b/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/LICENSE index a22e48ea..f9345c65 100644 --- a/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/LICENSE +++ b/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/LICENSE @@ -1,4 +1,3 @@ -{% if is_official_toolkit -%} # The Arcade Software License Agreement - Version 1.0 @@ -6,7 +5,7 @@ --- -This software and associated documentation (collectively, the “Software”) are the intellectual property of Arcade Technologies, Inc. (“Arcade”). All rights are reserved. +This software and associated documentation (collectively, the "Software") are the intellectual property of Arcade Technologies, Inc. ("Arcade"). All rights are reserved. 1. License Grant @@ -29,32 +28,8 @@ Arcade retains all right, title, and interest in and to the Software, including 5. Disclaimer of Warranty -The Software is provided “as is” without warranty of any kind. Arcade disclaims all warranties, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, and noninfringement. +The Software is provided "as is" without warranty of any kind. Arcade disclaims all warranties, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, and noninfringement. 6. Limitation of Liability In no event shall Arcade be liable for any damages arising out of or in connection with the use or performance of the Software, whether in an action of contract, tort (including negligence), or otherwise. -{% endif -%} -{% if is_community_toolkit -%} -MIT License - -Copyright (c) 2025, Arcade AI - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -{% endif -%} diff --git a/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/Makefile b/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/Makefile index 9ce9e50c..d3ef867c 100644 --- a/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/Makefile +++ b/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/Makefile @@ -1,57 +1,31 @@ -.PHONY: help +.PHONY: install build test lint dev -help: - @echo "🛠️ github Commands:\n" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +TOOLKIT := $(shell basename $(CURDIR)) -.PHONY: install -install: ## Install the uv environment and install all packages with dependencies - @echo "🚀 Creating virtual environment and installing all packages using uv" - @uv sync --active --all-extras --no-sources - @if [ -f .pre-commit-config.yaml ]; then uv run --no-sources pre-commit install; fi - @echo "✅ All packages and dependencies installed via uv" +help: ## Show this help message + @grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}' -{% if is_community_toolkit or is_official_toolkit -%} -.PHONY: install-local -install-local: ## Install the uv environment and install all packages with dependencies with local Arcade sources - @echo "🚀 Creating virtual environment and installing all packages using uv" - @uv sync --active --all-extras - @if [ -f .pre-commit-config.yaml ]; then uv run pre-commit install; fi - @echo "✅ All packages and dependencies installed via uv" -{% endif -%} +install: ## Install dependencies, then overlay any ../.local-overrides + uv sync --all-extras + uv run pre-commit install + @if [ -f ../.local-overrides ]; then \ + while IFS= read -r pkg || [ -n "$$pkg" ]; do \ + case "$$pkg" in \#*|"") continue ;; esac; \ + echo "Applying local override: $$pkg"; \ + uv pip install -e "$$pkg"; \ + done < ../.local-overrides; \ + fi -.PHONY: build -build: clean-build ## Build wheel file using poetry - @echo "🚀 Creating wheel file" +build: ## Build wheel + rm -rf dist uv build -.PHONY: clean-build -clean-build: ## clean build artifacts - @echo "🗑️ Cleaning dist directory" - rm -rf dist +test: ## Run tests with coverage + uv run pytest -W ignore -v --cov --cov-config=pyproject.toml --cov-report=xml -.PHONY: test -test: ## Test the code with pytest - @echo "🚀 Testing code: Running pytest" - @uv run --no-sources pytest -W ignore -v --cov --cov-config=pyproject.toml --cov-report=xml +lint: ## Run linting and type checking + uv run pre-commit run -a + uv run mypy --config-file=pyproject.toml -.PHONY: coverage -coverage: ## Generate coverage report - @echo "coverage report" - @uv run --no-sources coverage report - @echo "Generating coverage report" - @uv run --no-sources coverage html - -.PHONY: bump-version -bump-version: ## Bump the version in the pyproject.toml file by a patch version - @echo "🚀 Bumping version in pyproject.toml" - uv version --no-sources --bump patch - -.PHONY: check -check: ## Run code quality tools. - @if [ -f .pre-commit-config.yaml ]; then\ - echo "🚀 Linting code: Running pre-commit";\ - uv run --no-sources pre-commit run -a;\ - fi - @echo "🚀 Static type checking: Running mypy" - @uv run --no-sources mypy --config-file=pyproject.toml +dev: ## Run toolkit locally as MCP server + ARCADE_WORKER_SECRET=dev uv run arcade_$(TOOLKIT) http diff --git a/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/README.md b/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/README.md deleted file mode 100644 index 209d4b10..00000000 --- a/libs/arcade-cli/arcade_cli/templates/full/{{ toolkit_name }}/README.md +++ /dev/null @@ -1,40 +0,0 @@ -
-