Commit graph

237 commits

Author SHA1 Message Date
Eric Gustin
eeb47dbec5
[Toolkit Release] Weekly Toolkit Release 02-20-25 (#261)
# Weekly Toolkit Release 02-20-25
Previous Toolkit Release: #248 

## Google Toolkit Minor Release
https://github.com/ArcadeAI/arcade-ai/pull/249
#259 

## Slack Toolkit Minor Release
#254 

## X Toolkit Patch Release
#256
2025-02-20 13:27:35 -08:00
Eric Gustin
936319ccba
Fix langchain-arcade SyncOffsetPage iteration (#262)
## PR Description
The `ArcadeToolManager` was retrieving tools from the Arcade client
incorrectly as it was only returning the first page of results.

This PR removes the use of `SyncOffsetPage`'s `.items` attribute since
`.items` only contains the tools in the **current page**. Since
`SyncOffsetPage` implements an `__iter__` that iterates over all pages,
we can simply drop the `.items`.

## Try it for yourself
Run the following code without the changes in this PR and notice that
only 25 tools are in the internal tool list, which also happens to be
the size of a page. Now run again, but with the changes in this PR and
notice that all tools hosted by Arcade are in the internal tool list.
```python
import os

from langchain_arcade import ArcadeToolManager

arcade_api_key = os.environ.get("ARCADE_API_KEY")
manager = ArcadeToolManager(api_key=arcade_api_key)

tools = manager.init_tools()
print(len(manager.tools))
```
2025-02-20 13:24:03 -08:00
Nate Barbettini
274e63c9e5
Polish up Google.SearchContacts and CreateContact (#259)
Adding the missing polish (evals, tests) for #249
2025-02-20 08:53:12 -08:00
Eric Gustin
1e0def78df
CrewAI Arcade (#253)
# CrewAI Integration
crewai-arcade enables you to add Arcade tools and Arcade Auth into your
CrewAI applications. Just create an `ArcadeToolManager` and add your
tools to your CrewAI Agent/Tasks.

## Initializing the ArcadeToolManager
There are two main ways to initialize your `ArcadeToolManager`
1. Default handling of tool authorization and execution:
    ```py
    """
    When you provide a user id to the ArcadeToolManger, 
    it will handle the tool authorization and tool execution for you
    """
manager = ArcadeToolManager(default_user_id="me@example.com,
api_key="...")
    ```
2. Custom handling of tool authorization and execution
    ```py
    """
    Provide a callback function to the `ArcadeToolManager` that handles 
tool authorization and tool execution. The callback function will be
called whenever your CrewAI
    application wants to call a tool.
    """
    
    
    def custom_tool_executor(
manager: ArcadeToolManager, tool_name: str, **tool_input: dict[str, Any]
    ) -> Any:
        """Custom tool executor for the ArcadeToolManager
    
ArcadeToolManager's default executor handles authorization and tool
execution.
This function overrides the default executor to handle authorization and
tool execution
        in a custom way.
        """
        # Your custom tool auth logic goes here
        # Your custom tool execution logic goes here
        ...
    
manager = ArcadeToolManager(executor=custom_tool_executor,
api_key="...")
    ```

## Tool Registration
1. Initialize the tools in the manager
    ```py
    """
Clears any existing tools in the manager and replaces them with tools
and toolkits that are provided.
    """
    manager.init_tools(tools=["Google.ListEmails"], toolkits=["Slack"])
    ```
2. Add tools to the manager
    ```py
    """
    Adds tools and toolkits to the manager's internal tool list.
    """
    manager.add_tools(tools=["Google.ListEmails"], toolkits=["Slack"])
    ```
3. Retrieve tools and toolkits from the manager
    ```py
    """
    Retrieves the provided tools and toolkits as CrewAI StructuredTools.
    """
    manager.get_tools(tools=["Google.ListEmails"], toolkits=["Slack"])
    ```
    
 ## Auth Helpers
The `ArcadeToolManager` provides multiple helper methods for when you
need to create
a custom auth flow.
1. `authorize_tool` handles the whole authorization flow for you. This
is used internally when a custom auth flow is not needed.
2. `requires_auth(tool_name)` checks if the provided tool has
authorization requirements.
3. `authorize(tool_name, user_id)` authorizes the use of the provided
tool for the provided user ID
4. `is_authorized(tool_name, user_id)` checks if a tool is authorized
for use by the provided user ID
5. `wait_for_auth(auth_response)` waits for an authorization process to
complete before returning

## Tool Execution Helpers
1. `execute_tool` handles the whole tool execution flow for you. This is
used internally when a custom tool execution flow is not needed.

---------

Co-authored-by: lgesuellip <lgesuellipinto@uade.edu.ar>
Co-authored-by: lpetralli <123559656+lpetralli@users.noreply.github.com>
Co-authored-by: lgesuellip <102637283+lgesuellip@users.noreply.github.com>
Co-authored-by: “lgesuellip” <“lgesuellipinto@uade.edu.ar”>
2025-02-19 15:02:42 -08:00
Renato Byrro
8efa9a51df
Slack tools to retrieve messages & metadata from multi-person DM conversation (#254) 2025-02-19 16:51:45 -03:00
Nate Barbettini
becd86da0c
Google toolkit: Search and create contacts POC (#249)
This is an initial sketch of what Contacts (People) API tools could look
like. But I haven't yet thought like an MX Engineer @byrro 😉
2025-02-18 17:27:33 -08:00
Eric Gustin
3f655f7dca
Update README (#257)
Fixes some broken links in the Readme file
2025-02-18 13:53:34 -08:00
Eric Gustin
7d45a99722
X Toolkit: Handle no tweets returned in search (#256)
## PR Description
Search ([see API docs
here](https://docs.x.com/x-api/posts/recent-search)) returns a 'data'
field that maps to a list of tweets returned. We've observed that the
'data' field is not present if no tweets match the search. This PR
handles that case safely.
2025-02-18 13:28:33 -08:00
Eric Gustin
e636b686c1
Add @tool.deprecated (#247)
## PR Description
Add the ability to mark a tool as deprecated and display the warning in
the user's runtime. This PR also lays the foundation for future work for
emitting other levels of logs (debug, info, etc) that occur during the
tool's execution.

NOTE: Updates to the Arcade Clients (Python and JS) still need to be
done before the deprecation warning is emitted, but this PR needs to be
merged before those updates!

Let's cross our fingers that we'll never need to deprecate
`@tool.deprecated`!

### Example

1. Mark your tool as deprecated
```python
from typing import Annotated

from arcade.sdk import tool


@tool.deprecated("Use the 'Math.AddInt' tool instead.") # order of decorators does not matter
@tool
def add(
    a: Annotated[int, "The first number"], b: Annotated[int, "The second number"]
) -> Annotated[int, "The sum of the two numbers"]:
"""
Add two numbers together
"""
return a + b
```

2. Call the deprecated tool
```python
from arcadepy import Arcade

client = Arcade()

tool_input = {"a": 9001, "b": 42}

response = client.tools.execute(
    tool_name="Math.Add",
    input=tool_input,
    user_id="me@example.com",
)
print(f"The result of adding {tool_input['a']} and {tool_input['b']} is: {response.output.value}")
```

3. Observe the DeprecationWarning:
``` 
❯ python examples/call_a_tool_directly.py 
/Users/ericgustin/repos/Team/arcade-ai/examples/call_a_tool_directly.py:22: DeprecationWarning: 'Math.Add' is deprecated: Use the `Math.AddInt` tool instead.
  response = client.tools.execute(
The result of adding 9001 and 42 is: 9043
```
2025-02-18 13:27:49 -08:00
Eric Gustin
3870e84108
Workerup doesnt need to be logged in (#255) 2025-02-18 11:11:15 -08:00
Eric Gustin
49ff013e80
[Toolkit Release] Weekly Toolkit Release 02-13-25 (#248)
# Weekly Toolkit Release 02-13-25
Previous Toolkit Release: #236 

## Associated PRs
* Minor #241
2025-02-13 13:06:11 -08:00
Nate Barbettini
cb5cb7b730
fix: use correct UTF-8 encoding on Windows in arcade new (#246)
Fixes the linked issue. Tested on Windows 10
2025-02-12 14:50:46 -08:00
Sterling Dreyer
f31cabdf2b
Give release permissions (#245) 2025-02-10 16:07:25 -08:00
Eric Gustin
19086818d2
Make fastapi a regular dependency (#243)
## PR Description
Changes `pip install 'arcade-ai[fastapi]'` to `pip install arcade-ai`. 
In other words, FastAPI is now a required dependecy of arcade-ai.


Additionally, I snuck in some minor cleanup changes.
2025-02-10 15:30:51 -08:00
Renato Byrro
a00bd4734e
Tool to retrieve Slack messages from a DM conversation with a given username (#241)
Currently, retrieving DMs with a given username requires several
actions: first get the current user's ID; list all users and find the ID
of the username; then scan all DM conversations and find the one with
the current user's ID and the username's ID, to finally retrieve the
messages using that conversation ID.

This tool abstracts all that in a single call.

PS: we'll implement a similar tool for multi-person DM conversations in
a subsequent PR.
2025-02-10 09:01:04 -03:00
Eric Gustin
df969d9d73
Improve Arcade CLI (#244) 2025-02-08 10:56:04 -08:00
Eric Gustin
be2539602f
Evals New Features (#208)
# PR Description
This PR adds ~~four~~ three improvements to evals.

~~## 1. Add parameterized eval cases~~
~~Adds a new method named `add_parameterized_case`. Just like pytest’s
parameterized tests, eval cases can be parameterized with multiple user
messages. Adds a case to the `EvalSuite` for each user message. All
cases have the same expected tool call(s), params, additional_messages.
This reduces duplicate code and makes it easy to observe how a model
performs based on increasingly more difficult prompts.~~
```python
""" NO LONGER IN THIS PR
user_messages = [
    "Call the delete tweet by id tool with the tweet ID '148975632'.",
    "Delete the tweet with ID '148975632'.",
    "I don't want to have this tweet (148975632) on my account anymore.",
    "do the opposite of post for https://x.com/x/status/148975632",
]

suite.add_parameterized_case(
    name="Delete a tweet by ID",
    user_messages=user_messages,
    expected_tool_calls=[
        ExpectedToolCall(
            func=delete_tweet_by_id,
            args={"tweet_id": "148975632"},
        )
    ],
    critics=[
        BinaryCritic(
            critic_field="tweet_id",
            weight=1.0,
        ),
    ],
)
"""
```

~~PASSED Delete a tweet by ID (user_message 1 of 4) -- Score: 100.00%~~
~~PASSED Delete a tweet by ID (user_message 2 of 4) -- Score: 100.00%~~
~~PASSED Delete a tweet by ID (user_message 3 of 4) -- Score: 100.00%~~
~~FAILED Delete a tweet by ID (user_message 4 of 4) -- Score: 0.00%~~
~~Summary -- Total: 4 -- Passed: 3 -- Failed: 1~~

## 2. Parameters that are not explicitly criticized are assigned a
`NoneCritic`.
A NoneCritic has no effect on the evaluation results and does not
actually evaluate. Parameters that have a NoneCritic will be displayed
as ‘un-criticized’ in the evaluation summary (if `-d` flag is used).

![image](https://github.com/user-attachments/assets/300756ec-9b53-436a-9cf9-fc61d0b00c01)


## 3. Add a hardcoded `seed` parameter for evals.
The seed parameter aides in receiving (mostly) consistent outputs -
aiding in reproducibility for evaluations.

## 4. Disallow more than one critic for the same field.
Raises a `ValueError` if more than one critic is assigned to a field.

---------

Co-authored-by: Eric Gustin <eric@arcade-ai.com>
2025-02-05 15:22:08 -08:00
Renato Byrro
149c25d967
Fix missing scopes in tools that call other tools (#240) 2025-02-03 17:39:34 -08:00
Nate Barbettini
9d49572f09
Update CDN URL to cdn.arcade.dev (#238)
Updates our public CDN URL.
2025-01-30 11:58:41 -08:00
Eric Gustin
a344cc0a8b
Use cross platform paths (#237)
# PR Description
Tiny PR to use `os.path.join` instead of hardcoding paths with forward
slashes.
2025-01-30 09:51:57 -08:00
Eric Gustin
aaf1dbd795
[Toolkit Release] Weekly Toolkit Release 01-29-25 (#236)
# Weekly Toolkit Release 01-29-25
Previous Toolkit Release: #222 
## Associated PRs
Slack Patch: #232 
GitHub Patch: #227
2025-01-29 09:51:16 -08:00
Eric Gustin
ce2fb0f6c1
Update Examples & Various Renames (#233)
# PR Description
* This PR updates code in `examples/` to be compatible with version
1.0.0
* This PR removes the Spotify examples since the Arcade hosted worker
doesn't currently cataloge the Spotify toolkit. We can reintroduce these
examples when it does.
* This PR performs various renames across the codebase for
`arcade-ai.com` --> `arcade.dev` and `Arcade AI` --> `Arcade`
2025-01-28 17:17:29 -08:00
Sterling Dreyer
8226cb3316
Fix CICD test wait (#235) 2025-01-27 14:48:33 -08:00
Eric Gustin
3657fc79b6
Whitelist Toolkit Release Managers (#234)
# PR Description
The `github.event.pull_request.author_association` in the "Prevent
Unauthorized Version Updates" workflow was returning inconsistent
results by saying that MEMBERS were CONTRIBUTORS. This PR moves away
from `author_association` in favor of a whitelist text file containing
the GitHub usernames of authorized toolkit release managers.

A toolkit release manager has the following special permissions:
* Can change the version of an existing toolkit
* Can delete an existing toolkit 
* Can rename an existing toolkit
2025-01-27 14:35:45 -08:00
Renato Byrro
27d8aa7f43
Fix bug in slack tool pagination (#232)
The `get_conversation_metadata_by_name` tool retrieves conversation
metadata from another tool, `list_conversations_metadata`, but was
accessing the `next_cursor` using the Slack API response dict structure,
instead of the tool response structure. As a result, in that tool, the
tool would never actually paginate to the second page. This PR fixes it
and also adjust tests to capture the issue appropriately.
2025-01-27 12:23:00 -08:00
Sam Partee
778b7af83f
Update Langgraph examples (#231)
Clean up of the Langgraph examples that are used for the documentation.
Mostly just a restructuring for the update to
``langchain_arcade==1.0.0``
2025-01-26 23:38:59 -08:00
Sam Partee
7960158ee8
Update langchain integration to 1.0.0 (#230)
This PR updates the LangChain Arcade integration to v1.0.0, making the
following key changes:
• Bumped the package version in pyproject.toml from 0.2.0 to 1.0.0.
• Changed the default parameter in ArcadeToolManager from
langgraph=False to langgraph=True.
• Updated dependencies to require langgraph≥0.2.67,<0.3.0 and simplified
extras.
• Adjusted example scripts to remove explicit authorization_url
references in favor of a unified URL field.
• Updated docs and environment references to align with new usage
patterns and emphasize environment variables.
These changes unify and streamline the LangGraph-based tooling while
ensuring compatibility with the latest 1.0.0 release.
2025-01-26 22:23:14 -08:00
Nate Barbettini
4b5ce8d321
Fix dashboard redirect in docker compose (#229)
When the Engine is running via `docker compose`, the login redirect for
the Dashboard was ending up at `0.0.0.0` instead of `localhost`.
2025-01-24 16:40:33 -08:00
Renato Byrro
aa0cd02fe9
Make starred = True by default in Github tool (#227)
`starred` is a required argument of the
`arcade_github.activity.set_starred` tool, but when it is not provided
in the tool call, the engine is somehow passing it with a falsy value,
instead of raising an error. the falsy value makes the tool unstar a
repo by default, which is not the desired behavior.

we're setting the `starred` arg to True in the tool interface to prevent
that.
2025-01-24 13:50:30 -08:00
Sterling Dreyer
e78ff00831
Docker compose fixes (#226) 2025-01-24 10:29:15 -08:00
Nate Barbettini
a1db8a9f33
Fix login URL (fixes CSRF issues) (#225)
The CLI was starting the login process on the old domain, which is why
we were seeing a CSRF error. The cookie was placed on `arcade-ai.com`
not `arcade.dev`!
2025-01-23 22:44:57 -08:00
Eric Gustin
ca90b31262
Update README and LICENSE (#220)
Updates README to point to updated URLs

---------

Co-authored-by: Nate Barbettini <nate@arcade-ai.com>
2025-01-23 19:43:48 -08:00
Eric Gustin
4c95b7bebb
arcade-ai Version 1.0.0 (#224) 2025-01-23 19:28:35 -08:00
Eric Gustin
d5d6942ed1
Weekly Toolkit Release (#222)
# Relevant PRs
Google: #207 
Spotify: #204 

Slack: #162

Also relaxes arcade-ai dependency for all toolkits
2025-01-23 18:46:05 -08:00
Eric Gustin
6fb45c8797
Use AuthorizationResponse and ToolDefinition (#221) 2025-01-23 18:32:00 -08:00
Eric Gustin
66e54d7cde
Slack Tools (#162)
implements additional tools for Slack related to retrieving
conversations metadata, list of members, history of messages, as well as
sending messages to private/public channels and DMs / multi-person DMs.

---------

Co-authored-by: Eric Gustin <eric@arcade-ai.com>
Co-authored-by: Renato Byrro <rmbyrro@gmail.com>
2025-01-23 18:15:52 -08:00
Sterling Dreyer
130858a958
Ignore Toolkits (#219) 2025-01-23 15:37:15 -08:00
Sam Partee
09a0784cd5
Improve Docker Build and Deployment for Arcade Worker (#205)
This PR enhances the Docker build and deployment process for the Arcade
Worker by:

- **Modularizing Docker Builds:**
- Introduces a new `INSTALL_TOOLKITS` build argument in the `Dockerfile`
to conditionally include toolkits. this enables the creation of a
`arcadeai/worker-base` which can be used to build custom containers in a
multi-stage build. an example of this is included in the example dir.
- Adds `docker-base` Makefile target to build a lightweight base image
without toolkits.

- **Publishing to GitHub Container Registry (GHCR):**
- Adds Makefile targets `publish-ghcr` and `gh-login` for pushing images
to GHCR.
  - Supports publishing both base and full images with toolkits to GHCR.

- **Docker Compose:**
  - Add Docker compose file and setup
  - Renames the `actor` service to `worker`
- Adds an `nginx` service in `docker-compose.yml` to proxy requests to
the Arcade Engine.
  - Introduces an `nginx.conf` file for the Nginx service.

- **Streamlining Toolkit Installation:**
- Moves toolkit installation from the `start.sh` script to the Docker
build process.
- Creates a `toolkits.txt` file to manage toolkit dependencies which can
be edited easily when we want to add a new toolkit. Developers can also
use this approach as shown in the example.

- **Improving Configuration Files:**
- Updates `docker.engine.yaml` and `env.example` to align with the new
setup.


TODO:
- CI/CD needs to be adjusted so that images are pushed to ghcr on
release.
- AWS resources need to be renamed actor -> worker @EricGustin 

This can go in after the above two items are resolved.

---------

Co-authored-by: Wils Dawson <wils@arcade-ai.com>
Co-authored-by: Eric Gustin <34000337+EricGustin@users.noreply.github.com>
Co-authored-by: sdreyer <sterling@arcade-ai.com>
Co-authored-by: Sterling Dreyer <sdreyer21@gmail.com>
Co-authored-by: Nate Barbettini <nathanaelb@gmail.com>
2025-01-23 12:57:24 -08:00
Sam Partee
6d8e943c96
Update langchain integration to 0.2.0 (#213)
**PR Description**

This update bumps the integration’s version to `0.2.0` and brings
several important changes to how `langchain-arcade` interfaces with
Arcade tools:

1. **Updated Tool Definition Imports**  
• Replaces `arcadepy.types.shared.ToolDefinition` with
`arcadepy.types.ToolGetResponse as ToolDefinition`.
• The parameter extraction is now done via `tool_def.input.parameters`
instead of the previous `tool_def.inputs.parameters`.

2. **Authorization Flow Adjustments**  
• Uses `auth_response.url` instead of `auth_response.authorization_url`.
• The `authorize` and `is_authorized` methods now rely on the Arcade
client’s updated arguments (`client.auth.status(id=authorization_id)`).

3. **Tool Execution Parameter Renaming**  
• The `execute` method now expects `input=kwargs` instead of
`inputs=kwargs`, aligning with Arcade’s new API spec.

4. **Tool Retrieval Enhancements**  
• `_retrieve_tool_definitions` is revised to better handle pagination
and tool listing (including when no tools/toolkits are explicitly
provided).

5. **Version & Dependency Updates**  
   • Increases `langchain-arcade` to `0.2.0`.  
   • Switches `arcadepy` dependency to `~1.0.0rc1`.  
• Updates example requirements to consume
`langchain-arcade[langgraph]>=0.2.0`.

These changes may affect existing code that relies on older parameter
names (`inputs.parameters` → `input.parameters`) and the renamed execute
argument. Please ensure any integrations or custom usage of Arcade tools
is updated accordingly.
2025-01-22 13:01:15 -08:00
Sterling Dreyer
adaa0da649
Cleanup release action (#217) 2025-01-21 16:18:11 -08:00
Sterling Dreyer
b1ebd2afab
Fix pending test check (#216) 2025-01-21 15:57:24 -08:00
Eric Gustin
1bd8eac6ed
Add Ability to Disable Tools (#215)
# PR Description
This PR introduces a new environment variable `ARCADE_DISABLED_TOOLS`.
Tools that are added to this env var are not added to the worker's
`ToolCatalog`. In effect, they are disabled for the worker.

## How to use the `ARCADE_DISABLED_TOOLS` environment variable
* Each tool is separated by a comma.
* For each tool, specify the toolkit name in camel case and the tool
name in camel case.
* Do not include versions. (This is a simple implementation. We can add
disabling specific versions in the future if needed)
* Separate the toolkit name and the tool name with your environment's
tool name separator. By default, the tool name separator is `.`, but you
can override this with the `ARCADE_TOOL_NAME_SEPARATOR` environment
variable.

Correct: `export
ARCADE_DISABLED_TOOLS="Math.Add,Spotify.GetAvailableDevices,Math.Sqrt"`
Incorrect: `export
ARCADE_DISABLED_TOOLS="Math.Add@0.1.0,Spotify.get_available_devices,Sqrt`
2025-01-21 15:56:40 -08:00
Sterling Dreyer
899c84929b
Multiarch builds (#214)
Changes CICD to build the worker image on separate runners and push to
both GHCR and ECR
2025-01-21 15:49:24 -08:00
Sterling Dreyer
21ddeb1c93
Fix dev container builds (#212) 2025-01-20 11:32:23 -08:00
Eric Gustin
7a7f37e5fc
Add arcade --version (#211)
# PR Description
Implements a version callback for the arcade CLI.

### Usage
`arcade --version` OR `arcade -v`

### Example Output
`Arcade (version 0.1.5)`
2025-01-17 15:48:36 -08:00
Renato Byrro
830f6f9f90
Remove Spotify examples using deprecated tools (#199)
Refactor toolkit implementation examples following the removal of two
Spotify tools in
[PR#196](https://github.com/ArcadeAI/arcade-ai/pull/196), due to Spotify
API deprecation announcement.
2025-01-17 15:23:48 -08:00
Eric Gustin
6035cde920
Update auth provider_id (#173)
# PR Description
Well known providers (Google, X, Dropbox, etc.) can optionally have an
`id` in addition to their hardcoded `provider_id`. For non well known
providers, they must provide an `id`, and the `provider_id` is hardcoded
as `None`.

```python
OAuth2() # INVALID
OAuth2(provider_id="abc") # INVALID
OAuth2(id="abc") # VALID
OAuth2(provider_id="abc", id="def") # INVALID
```
```python
Google() # VALID
Google(provider_id="abc") # INVALID
Google(id="abc") # VALID
Google(provider_id="abc", id="def") # INVALID
```

---------

Co-authored-by: Wils Dawson <wils@arcade-ai.com>
2025-01-17 11:38:06 -08:00
Eric Gustin
cdd90b4844
Remove toml (#210)
# PR Description
`arcade.toml` was deprecated in favor of `credentials.yaml` in PR #116 .
This PR completely removes any references to handling the deprecation &
any references to `arcade.toml`
2025-01-17 09:56:43 -08:00
Eric Gustin
3f9da98560
Updates for arcadepy1.0.0rc Compatibility (#209)
# PR Description
arcadepy version 1.0.0rc renamed `AuthorizationResponse` to
`AuthAuthorizationResponse`
2025-01-17 09:26:11 -08:00
Eric Gustin
e9d630f5dc
Check for unauthorized toolkit changes (#200)
# PR Description
Add checks for unauthorized actions performed on toolkits. A PR cannot
be merged if it contains an unauthorized toolkit action.

An unauthorized toolkit action is defined as:
1. The author of the PR is not an OWNER or a MEMBER of the ArcadeAI
organization
2. AND one of the following applies:
- A toolkit's version is altered (i.e., the version in
`toolkits/*/pyproject.toml`
    - A toolkit is deleted
- A toolkit's PyPI package name is renamed (i.e., the name in
`toolkits/*/pyproject.toml`
2025-01-16 17:28:20 -08:00