diff --git a/autogpt_platform/backend/backend/blocks/github/pull_requests.py b/autogpt_platform/backend/backend/blocks/github/pull_requests.py
index 905b6e346..f890ba0d5 100644
--- a/autogpt_platform/backend/backend/blocks/github/pull_requests.py
+++ b/autogpt_platform/backend/backend/blocks/github/pull_requests.py
@@ -732,3 +732,201 @@ class GithubUpdateFileBlock(Block):
yield "sha", sha
except Exception as e:
yield "error", str(e)
+
+
+class GithubCreateRepositoryBlock(Block):
+ class Input(BlockSchema):
+ credentials: GithubCredentialsInput = GithubCredentialsField("repo")
+ name: str = SchemaField(
+ description="Name of the repository to create",
+ placeholder="my-new-repo",
+ )
+ description: str = SchemaField(
+ description="Description of the repository",
+ placeholder="A description of the repository",
+ default="",
+ )
+ private: bool = SchemaField(
+ description="Whether the repository should be private",
+ default=False,
+ )
+ auto_init: bool = SchemaField(
+ description="Whether to initialize the repository with a README",
+ default=True,
+ )
+ gitignore_template: str = SchemaField(
+ description="Git ignore template to use (e.g., Python, Node, Java)",
+ default="",
+ )
+
+ class Output(BlockSchema):
+ url: str = SchemaField(description="URL of the created repository")
+ clone_url: str = SchemaField(description="Git clone URL of the repository")
+ error: str = SchemaField(
+ description="Error message if the repository creation failed"
+ )
+
+ def __init__(self):
+ super().__init__(
+ id="029ec3b8-1cfd-46d3-b6aa-28e4a706efd1",
+ description="This block creates a new GitHub repository.",
+ categories={BlockCategory.DEVELOPER_TOOLS},
+ input_schema=GithubCreateRepositoryBlock.Input,
+ output_schema=GithubCreateRepositoryBlock.Output,
+ test_input={
+ "name": "test-repo",
+ "description": "A test repository",
+ "private": False,
+ "auto_init": True,
+ "gitignore_template": "Python",
+ "credentials": TEST_CREDENTIALS_INPUT,
+ },
+ test_credentials=TEST_CREDENTIALS,
+ test_output=[
+ ("url", "https://github.com/owner/test-repo"),
+ ("clone_url", "https://github.com/owner/test-repo.git"),
+ ],
+ test_mock={
+ "create_repository": lambda *args, **kwargs: (
+ "https://github.com/owner/test-repo",
+ "https://github.com/owner/test-repo.git",
+ )
+ },
+ )
+
+ @staticmethod
+ def create_repository(
+ credentials: GithubCredentials,
+ name: str,
+ description: str,
+ private: bool,
+ auto_init: bool,
+ gitignore_template: str,
+ ) -> tuple[str, str]:
+ api = get_api(credentials, convert_urls=False) # Disable URL conversion
+ data = {
+ "name": name,
+ "description": description,
+ "private": private,
+ "auto_init": auto_init,
+ }
+
+ if gitignore_template:
+ data["gitignore_template"] = gitignore_template
+
+ # Create repository using the user endpoint
+ response = api.post("https://api.github.com/user/repos", json=data)
+ result = response.json()
+
+ return result["html_url"], result["clone_url"]
+
+ def run(
+ self,
+ input_data: Input,
+ *,
+ credentials: GithubCredentials,
+ **kwargs,
+ ) -> BlockOutput:
+ try:
+ url, clone_url = self.create_repository(
+ credentials,
+ input_data.name,
+ input_data.description,
+ input_data.private,
+ input_data.auto_init,
+ input_data.gitignore_template,
+ )
+ yield "url", url
+ yield "clone_url", clone_url
+ except Exception as e:
+ yield "error", str(e)
+
+
+class GithubListStargazersBlock(Block):
+ class Input(BlockSchema):
+ credentials: GithubCredentialsInput = GithubCredentialsField("repo")
+ repo_url: str = SchemaField(
+ description="URL of the GitHub repository",
+ placeholder="https://github.com/owner/repo",
+ )
+
+ class Output(BlockSchema):
+ class StargazerItem(TypedDict):
+ username: str
+ url: str
+
+ stargazer: StargazerItem = SchemaField(
+ title="Stargazer",
+ description="Stargazers with their username and profile URL",
+ )
+ error: str = SchemaField(
+ description="Error message if listing stargazers failed"
+ )
+
+ def __init__(self):
+ super().__init__(
+ id="a4b9c2d1-e5f6-4g7h-8i9j-0k1l2m3n4o5p", # Generated unique UUID
+ description="This block lists all users who have starred a specified GitHub repository.",
+ categories={BlockCategory.DEVELOPER_TOOLS},
+ input_schema=GithubListStargazersBlock.Input,
+ output_schema=GithubListStargazersBlock.Output,
+ test_input={
+ "repo_url": "https://github.com/owner/repo",
+ "credentials": TEST_CREDENTIALS_INPUT,
+ },
+ test_credentials=TEST_CREDENTIALS,
+ test_output=[
+ (
+ "stargazer",
+ {
+ "username": "octocat",
+ "url": "https://github.com/octocat",
+ },
+ )
+ ],
+ test_mock={
+ "list_stargazers": lambda *args, **kwargs: [
+ {
+ "username": "octocat",
+ "url": "https://github.com/octocat",
+ }
+ ]
+ },
+ )
+
+ @staticmethod
+ def list_stargazers(
+ credentials: GithubCredentials, repo_url: str
+ ) -> list[Output.StargazerItem]:
+ api = get_api(credentials)
+ # Add /stargazers to the repo URL to get stargazers endpoint
+ stargazers_url = f"{repo_url}/stargazers"
+ # Set accept header to get starred_at timestamp
+ headers = {"Accept": "application/vnd.github.star+json"}
+ response = api.get(stargazers_url, headers=headers)
+ data = response.json()
+
+ stargazers: list[GithubListStargazersBlock.Output.StargazerItem] = [
+ {
+ "username": stargazer["login"],
+ "url": stargazer["html_url"],
+ }
+ for stargazer in data
+ ]
+ return stargazers
+
+ def run(
+ self,
+ input_data: Input,
+ *,
+ credentials: GithubCredentials,
+ **kwargs,
+ ) -> BlockOutput:
+ try:
+ stargazers = self.list_stargazers(
+ credentials,
+ input_data.repo_url,
+ )
+ yield from (("stargazer", stargazer) for stargazer in stargazers)
+ except Exception as e:
+ yield "error", str(e)
diff --git a/autogpt_platform/backend/backend/util/request.py b/autogpt_platform/backend/backend/util/request.py
index 10b716f2c..9a3fa0c5e 100644
--- a/autogpt_platform/backend/backend/util/request.py
+++ b/autogpt_platform/backend/backend/util/request.py
@@ -33,20 +33,6 @@ ALLOWED_SCHEMES = ["http", "https"]
HOSTNAME_REGEX = re.compile(r"^[A-Za-z0-9.-]+$") # Basic DNS-safe hostname pattern
-def _canonicalize_url(url: str) -> str:
- """
- Normalizes the URL by:
- 1. Stripping whitespace and trailing slashes.
- 2. Ensuring the scheme is http:// or https:// if missing.
- 3. Replacing backslashes with forward slashes.
- """
- url = url.strip().strip("/")
- if not url.startswith(("http://", "https://")):
- url = "http://" + url
- url = url.replace("\\", "/")
- return url
-
-
def _is_ip_blocked(ip: str) -> bool:
"""
Checks if the IP address is in a blocked network.
@@ -61,9 +47,12 @@ def validate_url(url: str, trusted_origins: list[str]) -> str:
to a private, link-local, or otherwise blocked IP address — unless
the hostname is explicitly trusted.
"""
- # Normalize/canonicalize input
- url = _canonicalize_url(url)
+ # Canonicalize URL
+ url = url.strip("/ ").replace("\\", "/")
parsed = urlparse(url)
+ if not parsed.scheme:
+ url = f"http://{url}"
+ parsed = urlparse(url)
# Check scheme
if parsed.scheme not in ALLOWED_SCHEMES:
diff --git a/autogpt_platform/frontend/package.json b/autogpt_platform/frontend/package.json
index 1d3545133..f3fbd5385 100644
--- a/autogpt_platform/frontend/package.json
+++ b/autogpt_platform/frontend/package.json
@@ -81,7 +81,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
- "@chromatic-com/storybook": "^3.2.2",
+ "@chromatic-com/storybook": "^3.2.3",
"@playwright/test": "^1.48.2",
"@storybook/addon-a11y": "^8.3.5",
"@storybook/addon-essentials": "^8.4.2",
@@ -92,7 +92,7 @@
"@storybook/nextjs": "^8.4.2",
"@storybook/react": "^8.3.5",
"@storybook/test": "^8.3.5",
- "@storybook/test-runner": "^0.20.1",
+ "@storybook/test-runner": "^0.21.0",
"@types/negotiator": "^0.6.3",
"@types/node": "^22.9.0",
"@types/react": "^18",
@@ -100,9 +100,9 @@
"@types/react-modal": "^3.16.3",
"axe-playwright": "^2.0.3",
"chromatic": "^11.12.5",
- "concurrently": "^9.0.1",
+ "concurrently": "^9.1.1",
"eslint": "^8",
- "eslint-config-next": "15.1.0",
+ "eslint-config-next": "15.1.3",
"eslint-plugin-storybook": "^0.11.0",
"msw": "^2.7.0",
"msw-storybook-addon": "^2.0.3",
@@ -110,7 +110,7 @@
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.9",
"storybook": "^8.4.5",
- "tailwindcss": "^3.4.15",
+ "tailwindcss": "^3.4.17",
"typescript": "^5"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
diff --git a/autogpt_platform/frontend/src/components/CustomNode.tsx b/autogpt_platform/frontend/src/components/CustomNode.tsx
index 735abe555..7d2e6c54b 100644
--- a/autogpt_platform/frontend/src/components/CustomNode.tsx
+++ b/autogpt_platform/frontend/src/components/CustomNode.tsx
@@ -726,13 +726,10 @@ export function CustomNode({
{/* Body */}
-
+
{/* Input Handles */}
{data.uiType !== BlockUIType.NOTE ? (
-
+
{data.uiType === BlockUIType.WEBHOOK_MANUAL &&
(data.webhook ? (
@@ -781,7 +778,6 @@ export function CustomNode({
>
@@ -790,7 +786,7 @@ export function CustomNode({
{data.uiType !== BlockUIType.NOTE && (
<>
-
+
{data.outputSchema &&
generateOutputHandles(data.outputSchema, data.uiType)}
diff --git a/autogpt_platform/frontend/src/components/NodeHandle.tsx b/autogpt_platform/frontend/src/components/NodeHandle.tsx
index 137e5e4c3..f733c1763 100644
--- a/autogpt_platform/frontend/src/components/NodeHandle.tsx
+++ b/autogpt_platform/frontend/src/components/NodeHandle.tsx
@@ -82,7 +82,7 @@ const NodeHandle: FC
= ({
data-testid={`output-handle-${keyName}`}
position={Position.Right}
id={keyName}
- className="group -mr-[26px]"
+ className="group -mr-[38px]"
>
{label}
diff --git a/autogpt_platform/frontend/src/components/customnode.css b/autogpt_platform/frontend/src/components/customnode.css
index d947540f4..8e4ed0c87 100644
--- a/autogpt_platform/frontend/src/components/customnode.css
+++ b/autogpt_platform/frontend/src/components/customnode.css
@@ -15,15 +15,11 @@
.custom-node [data-id^="date-picker"],
.custom-node [data-list-container],
.custom-node [data-add-item],
-.custom-node [data-content-settings] {
- min-width: calc(100% - 2.5rem);
- max-width: 400px;
-}
-
-.array-item-container {
+.custom-node [data-content-settings]. .array-item-container {
display: flex;
align-items: center;
min-width: calc(100% - 2.5rem);
+ max-width: 100%;
}
.custom-node .custom-switch {
diff --git a/autogpt_platform/frontend/src/components/node-input-components.tsx b/autogpt_platform/frontend/src/components/node-input-components.tsx
index 04999420f..b97e2ed4e 100644
--- a/autogpt_platform/frontend/src/components/node-input-components.tsx
+++ b/autogpt_platform/frontend/src/components/node-input-components.tsx
@@ -201,7 +201,7 @@ export const NodeGenericInputField: FC<{
className,
displayName,
}) => {
- className = cn(className, "my-2");
+ className = cn(className);
displayName ||= propSchema.title || beautifyString(propKey);
if ("allOf" in propSchema) {
@@ -876,18 +876,19 @@ const NodeArrayInput: FC<{
(c) => c.targetHandle === entryKey && c.target === nodeId,
);
return (
-
+
+
-
{!isConnected &&
(schema.items ? (
=6.0.0,<7.0.0)"]
[[package]]
name = "pyright"
-version = "1.1.390"
+version = "1.1.391"
description = "Command line wrapper for pyright"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pyright-1.1.390-py3-none-any.whl", hash = "sha256:ecebfba5b6b50af7c1a44c2ba144ba2ab542c227eb49bc1f16984ff714e0e110"},
- {file = "pyright-1.1.390.tar.gz", hash = "sha256:aad7f160c49e0fbf8209507a15e17b781f63a86a1facb69ca877c71ef2e9538d"},
+ {file = "pyright-1.1.391-py3-none-any.whl", hash = "sha256:54fa186f8b3e8a55a44ebfa842636635688670c6896dcf6cf4a7fc75062f4d15"},
+ {file = "pyright-1.1.391.tar.gz", hash = "sha256:66b2d42cdf5c3cbab05f2f4b76e8bec8aa78e679bfa0b6ad7b923d9e027cadb2"},
]
[package.dependencies]
@@ -871,13 +871,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments
[[package]]
name = "pytest-asyncio"
-version = "0.25.0"
+version = "0.25.1"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.9"
files = [
- {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"},
- {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"},
+ {file = "pytest_asyncio-0.25.1-py3-none-any.whl", hash = "sha256:c84878849ec63ff2ca509423616e071ef9cd8cc93c053aa33b5b8fb70a990671"},
+ {file = "pytest_asyncio-0.25.1.tar.gz", hash = "sha256:79be8a72384b0c917677e00daa711e07db15259f4d23203c59012bcd989d4aee"},
]
[package.dependencies]
@@ -1058,29 +1058,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "ruff"
-version = "0.8.3"
+version = "0.8.4"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"},
- {file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"},
- {file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"},
- {file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"},
- {file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"},
- {file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"},
- {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"},
- {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"},
- {file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"},
- {file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"},
- {file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"},
- {file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"},
- {file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"},
- {file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"},
- {file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"},
- {file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"},
- {file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"},
- {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"},
+ {file = "ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60"},
+ {file = "ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac"},
+ {file = "ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296"},
+ {file = "ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643"},
+ {file = "ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e"},
+ {file = "ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3"},
+ {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f"},
+ {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604"},
+ {file = "ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf"},
+ {file = "ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720"},
+ {file = "ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae"},
+ {file = "ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7"},
+ {file = "ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111"},
+ {file = "ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8"},
+ {file = "ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835"},
+ {file = "ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d"},
+ {file = "ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08"},
+ {file = "ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8"},
]
[[package]]
@@ -1298,4 +1298,4 @@ watchmedo = ["PyYAML (>=3.10)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "5dbf6cd95ba8e80c4a6b4e6a54c6cdfb1488619e4293d1d5a8572c5330485493"
+content-hash = "c62380410681d30c5c5da8b047c449c92196f2a25ea5d353db2a3e5470737513"
diff --git a/autogpt_platform/market/pyproject.toml b/autogpt_platform/market/pyproject.toml
index 279a11116..d89695abf 100644
--- a/autogpt_platform/market/pyproject.toml
+++ b/autogpt_platform/market/pyproject.toml
@@ -24,12 +24,12 @@ prometheus-fastapi-instrumentator = "^7.0.0"
autogpt-libs = {path = "../autogpt_libs"}
[tool.poetry.group.dev.dependencies]
pytest = "^8.3.4"
-pytest-asyncio = "^0.25.0"
+pytest-asyncio = "^0.25.1"
pytest-watcher = "^0.4.3"
requests = "^2.32.3"
-ruff = "^0.8.3"
-pyright = "^1.1.390"
+ruff = "^0.8.4"
+pyright = "^1.1.391"
isort = "^5.13.2"
black = "^24.10.0"