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"