a7d6d88f6f
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
187 lines
6.8 KiB
Python
187 lines
6.8 KiB
Python
import os
|
|
import shutil
|
|
import sys
|
|
from io import BytesIO
|
|
from urllib import error, request
|
|
from zipfile import ZipFile
|
|
|
|
import click
|
|
|
|
TEMPLATES: dict[str, dict[str, str]] = {
|
|
"Deep Agent": {
|
|
"description": "An opinionated deployment template for a Deep Agent.",
|
|
"python": "https://github.com/langchain-ai/deep-agent-template/archive/refs/heads/main.zip",
|
|
"js": "https://github.com/langchain-ai/deep-agent-template-js/archive/refs/heads/main.zip",
|
|
},
|
|
"Agent": {
|
|
"description": "A simple agent that can be flexibly extended to many tools.",
|
|
"python": "https://github.com/langchain-ai/simple-agent-template/archive/refs/heads/main.zip",
|
|
},
|
|
"New LangGraph Project": {
|
|
"description": "A simple, minimal chatbot with memory.",
|
|
"python": "https://github.com/langchain-ai/new-langgraph-project/archive/refs/heads/main.zip",
|
|
"js": "https://github.com/langchain-ai/new-langgraphjs-project/archive/refs/heads/main.zip",
|
|
},
|
|
}
|
|
|
|
# Generate TEMPLATE_IDS programmatically
|
|
TEMPLATE_ID_TO_CONFIG = {
|
|
f"{name.lower().replace(' ', '-')}-{lang}": (name, lang, url)
|
|
for name, versions in TEMPLATES.items()
|
|
for lang, url in versions.items()
|
|
if lang in {"python", "js"}
|
|
}
|
|
|
|
TEMPLATE_IDS = list(TEMPLATE_ID_TO_CONFIG.keys())
|
|
|
|
TEMPLATE_HELP_STRING = (
|
|
"The name of the template to use. Available options:\n"
|
|
+ "\n".join(f"{id_}" for id_ in TEMPLATE_ID_TO_CONFIG)
|
|
)
|
|
|
|
|
|
def _choose_template() -> str:
|
|
"""Presents a list of templates to the user and prompts them to select one.
|
|
|
|
Returns:
|
|
str: The URL of the selected template.
|
|
"""
|
|
click.secho("🌟 Please select a template:", bold=True, fg="yellow")
|
|
for idx, (template_name, template_info) in enumerate(TEMPLATES.items(), 1):
|
|
click.secho(f"{idx}. ", nl=False, fg="cyan")
|
|
click.secho(template_name, fg="cyan", nl=False)
|
|
click.secho(f" - {template_info['description']}", fg="white")
|
|
|
|
# Get the template choice from the user, defaulting to the first template if blank
|
|
template_choice: int | None = click.prompt(
|
|
"Enter the number of your template choice (default is 1)",
|
|
type=int,
|
|
default=1,
|
|
show_default=False,
|
|
)
|
|
|
|
template_keys = list(TEMPLATES.keys())
|
|
if 1 <= template_choice <= len(template_keys):
|
|
selected_template: str = template_keys[template_choice - 1]
|
|
else:
|
|
click.secho("❌ Invalid choice. Please try again.", fg="red")
|
|
return _choose_template()
|
|
|
|
template_info = TEMPLATES[selected_template]
|
|
available_langs = [lang for lang in ("python", "js") if lang in template_info]
|
|
|
|
click.secho(
|
|
f"\nYou selected: {selected_template} - {template_info['description']}",
|
|
fg="green",
|
|
)
|
|
|
|
if len(available_langs) == 1:
|
|
return template_info[available_langs[0]]
|
|
|
|
version_choice: int = click.prompt(
|
|
"Choose language (1 for Python 🐍, 2 for JS/TS 🌐)", type=int
|
|
)
|
|
|
|
if version_choice == 1:
|
|
return template_info["python"]
|
|
elif version_choice == 2:
|
|
return template_info["js"]
|
|
else:
|
|
click.secho("❌ Invalid choice. Please try again.", fg="red")
|
|
return _choose_template()
|
|
|
|
|
|
def _download_repo_with_requests(repo_url: str, path: str) -> None:
|
|
"""Download a ZIP archive from the given URL and extracts it to the specified path.
|
|
|
|
Args:
|
|
repo_url: The URL of the repository to download.
|
|
path: The path where the repository should be extracted.
|
|
"""
|
|
click.secho("📥 Attempting to download repository as a ZIP archive...", fg="yellow")
|
|
click.secho(f"URL: {repo_url}", fg="yellow")
|
|
try:
|
|
with request.urlopen(repo_url) as response:
|
|
if response.status == 200:
|
|
with ZipFile(BytesIO(response.read())) as zip_file:
|
|
zip_file.extractall(path)
|
|
# Move extracted contents to path
|
|
for item in os.listdir(path):
|
|
if item.endswith("-main"):
|
|
extracted_dir = os.path.join(path, item)
|
|
for filename in os.listdir(extracted_dir):
|
|
shutil.move(os.path.join(extracted_dir, filename), path)
|
|
shutil.rmtree(extracted_dir)
|
|
click.secho(
|
|
f"✅ Downloaded and extracted repository to {path}", fg="green"
|
|
)
|
|
except error.HTTPError as e:
|
|
click.secho(
|
|
f"❌ Error: Failed to download repository.\nDetails: {e}\n",
|
|
fg="red",
|
|
bold=True,
|
|
err=True,
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
def create_new(path: str | None, template: str | None) -> None:
|
|
"""Create a new LangGraph project at the specified PATH using the chosen TEMPLATE.
|
|
|
|
Args:
|
|
path: The path where the new project will be created.
|
|
template: The name of the template to use.
|
|
"""
|
|
# Prompt for path if not provided
|
|
if not path:
|
|
path = click.prompt(
|
|
"📂 Please specify the path to create the application", default="."
|
|
)
|
|
|
|
path = os.path.abspath(path) # Ensure path is absolute
|
|
|
|
# Check if path exists and is not empty
|
|
if os.path.exists(path) and os.listdir(path):
|
|
click.secho(
|
|
"❌ The specified directory already exists and is not empty. "
|
|
"Aborting to prevent overwriting files.",
|
|
fg="red",
|
|
bold=True,
|
|
)
|
|
sys.exit(1)
|
|
|
|
# Get template URL either from command-line argument or
|
|
# through interactive selection
|
|
if template:
|
|
if template not in TEMPLATE_ID_TO_CONFIG:
|
|
# Format available options in a readable way with descriptions
|
|
template_options = ""
|
|
for id_ in TEMPLATE_IDS:
|
|
name, lang, _ = TEMPLATE_ID_TO_CONFIG[id_]
|
|
description = TEMPLATES[name]["description"]
|
|
|
|
# Add each template option with color formatting
|
|
template_options += (
|
|
click.style("- ", fg="yellow", bold=True)
|
|
+ click.style(f"{id_}", fg="cyan")
|
|
+ click.style(f": {description}", fg="white")
|
|
+ "\n"
|
|
)
|
|
|
|
# Display error message with colors and formatting
|
|
click.secho("❌ Error:", fg="red", bold=True, nl=False)
|
|
click.secho(f" Template '{template}' not found.", fg="red")
|
|
click.secho(
|
|
"Please select from the available options:\n", fg="yellow", bold=True
|
|
)
|
|
click.secho(template_options, fg="cyan")
|
|
sys.exit(1)
|
|
_, _, template_url = TEMPLATE_ID_TO_CONFIG[template]
|
|
else:
|
|
template_url = _choose_template()
|
|
|
|
# Download and extract the template
|
|
_download_repo_with_requests(template_url, path)
|
|
|
|
click.secho(f"🎉 New project created at {path}", fg="green", bold=True)
|