Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions toolkits/todoist/community/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
files: ^.*/todoist/.*
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.4.0"
hooks:
- id: check-case-conflict
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.7
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
44 changes: 44 additions & 0 deletions toolkits/todoist/community/.ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
target-version = "py310"
line-length = 100
fix = true

[lint]
select = [
# flake8-2020
"YTT",
# flake8-bandit
"S",
# flake8-bugbear
"B",
# flake8-builtins
"A",
# flake8-comprehensions
"C4",
# flake8-debugger
"T10",
# flake8-simplify
"SIM",
# isort
"I",
# mccabe
"C90",
# pycodestyle
"E", "W",
# pyflakes
"F",
# pygrep-hooks
"PGH",
# pyupgrade
"UP",
# ruff
"RUF",
# tryceratops
"TRY",
]

[lint.per-file-ignores]
"**/tests/*" = ["S101"]

[format]
preview = true
skip-magic-trailing-comma = false
55 changes: 55 additions & 0 deletions toolkits/todoist/community/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.PHONY: help

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}'

.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"

.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"

.PHONY: build
build: clean-build ## Build wheel file using poetry
@echo "🚀 Creating wheel file"
uv build

.PHONY: clean-build
clean-build: ## clean build artifacts
@echo "🗑️ Cleaning dist directory"
rm -rf dist

.PHONY: test
test: ## Test the code with pytest
@echo "🚀 Testing code: Running pytest"
@uv run --no-sources pytest -W ignore -vv --cov --cov-config=pyproject.toml --cov-report=xml

.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
26 changes: 26 additions & 0 deletions toolkits/todoist/community/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div style="display: flex; justify-content: center; align-items: center;">
<img
src="https://docs.arcade.dev/images/logo/arcade-logo.png"
style="width: 250px;"
>
</div>

<div style="display: flex; justify-content: center; align-items: center; margin-bottom: 8px;">
<img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python version" style="margin: 0 2px;">
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License" style="margin: 0 2px;">
<img src="https://img.shields.io/pypi/v/arcade_todoist" alt="PyPI version" style="margin: 0 2px;">
</div>


<br>
<br>

# Arcade todoist Toolkit
Allow agent to connect and interact with Todoist
## Features

- The todoist toolkit does not have any features yet.

## Development

Read the docs on how to create a toolkit [here](https://docs.arcade.dev/home/build-tools/create-a-toolkit)
Empty file.
59 changes: 59 additions & 0 deletions toolkits/todoist/community/arcade_todoist/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from arcade_tdk.errors import ToolExecutionError


class ProjectNotFoundError(ToolExecutionError):
"""Raised when a project is not found."""

def __init__(self, project_name: str, partial_matches: list[str] | None = None):
if partial_matches:
matches_str = "', '".join(partial_matches)
super().__init__(
"Project not found",
developer_message=(
f"Project '{project_name}' not found, but found partial matches: "
f"{matches_str}. "
f"Please specify the exact project name."
),
)
else:
super().__init__(
"Project not found",
developer_message=(
f"Project '{project_name}' not found. "
f"Ask the user to create the project first."
),
)


class TaskNotFoundError(ToolExecutionError):
"""Raised when a task is not found."""

def __init__(self, task_description: str, partial_matches: list[str] | None = None):
if partial_matches:
matches_str = "', '".join(partial_matches)
super().__init__(
"Task not found",
developer_message=(
f"Task '{task_description}' not found, but found partial matches: "
f"{matches_str}. "
f"Please specify the exact task description."
),
)
else:
super().__init__(
"Task not found", developer_message=f"Task '{task_description}' not found."
)


class MultipleTasksFoundError(ToolExecutionError):
"""Raised when multiple tasks match the search criteria."""

def __init__(self, task_description: str, task_matches: list[dict]):
matches_str = "', '".join([task["content"] for task in task_matches])
super().__init__(
"Multiple tasks found",
developer_message=(
f"Multiple tasks found for '{task_description}': '{matches_str}'. "
f"Please specify the exact task description to choose one."
),
)
Empty file.
35 changes: 35 additions & 0 deletions toolkits/todoist/community/arcade_todoist/tools/projects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Annotated

import httpx
from arcade_tdk import ToolContext, tool
from arcade_tdk.auth import OAuth2

from arcade_todoist.utils import get_headers, get_url, parse_projects


@tool(
requires_auth=OAuth2(
id="todoist",
scopes=["data:read"],
),
)
async def get_projects(
context: ToolContext,
) -> Annotated[dict, "The projects object returned by the Todoist API."]:
"""
Get all projects from the Todoist API. Use this when the user wants to see, list, or browse
their projects. Do NOT use this for creating tasks - use create_task instead even if a
project name is mentioned.
"""

async with httpx.AsyncClient() as client:
url = get_url(context=context, endpoint="projects")
headers = get_headers(context=context)

response = await client.get(url, headers=headers)

response.raise_for_status()

projects = parse_projects(response.json()["results"])

return {"projects": projects}
Loading