tasksmind 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Environment variables
16
+ .env
17
+ .env.local
18
+ .env.development.local
19
+ .env.test.local
20
+ .env.production.local
21
+ supabase/.env
22
+
23
+ # Editor directories and files
24
+ .vscode/*
25
+ !.vscode/extensions.json
26
+ .idea
27
+ .DS_Store
28
+ *.suo
29
+ *.ntvs*
30
+ *.njsproj
31
+ *.sln
32
+ *.sw?
33
+
34
+ .env
35
+ .env.local
36
+ # Python
37
+ __pycache__/
38
+ *.pyc
39
+ .modal.toml
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: tasksmind
3
+ Version: 0.1.0
4
+ Summary: TasksMind Python SDK — The AI Engineer for Developers on Call.
5
+ Project-URL: Homepage, https://tasksmind.com
6
+ Project-URL: Documentation, https://docs.tasksmind.com
7
+ Project-URL: Repository, https://github.com/tasksmind/tasksmind-python
8
+ Project-URL: Bug Tracker, https://github.com/tasksmind/tasksmind-python/issues
9
+ Author-email: TasksMind <support@tasksmind.com>
10
+ License: MIT
11
+ Keywords: ai,automation,devops,oncall,tasksmind
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: httpx>=0.24.0
23
+ Description-Content-Type: text/markdown
24
+
25
+ # TasksMind Python SDK
26
+
27
+ The official Python client for the [TasksMind API](https://tasksmind.com) — The AI Engineer for Developers on Call.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install tasksmind
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ import os
39
+ from tasksmind import TasksMind
40
+
41
+ client = TasksMind(api_key=os.environ["TASKSMIND_API_KEY"])
42
+
43
+ # Kick off a PR review
44
+ run = client.runs.create(
45
+ repo_url="https://github.com/my-org/my-repo",
46
+ repo_ref="main",
47
+ payload={
48
+ "intent": "review_pr",
49
+ "target": {"pr_number": 42},
50
+ },
51
+ )
52
+ print(f"Run started: {run.id}")
53
+
54
+ # Poll until complete (up to 10 minutes)
55
+ result = client.runs.wait(run.id, timeout_s=600)
56
+ if result.is_success():
57
+ print(result.output)
58
+ else:
59
+ print(f"Run failed with status: {result.status}")
60
+ ```
61
+
62
+ ## Configuration
63
+
64
+ | Parameter | Env variable | Default |
65
+ |-----------|-------------|---------|
66
+ | `api_key` | `TASKSMIND_API_KEY` | — (required) |
67
+ | `base_url` | `TASKSMIND_API_BASE_URL` | `https://api.tasksmind.com` |
68
+ | `timeout` | — | `60` seconds |
69
+
70
+ ## Available Resources
71
+
72
+ ### `client.runs`
73
+
74
+ | Method | Description |
75
+ |--------|-------------|
76
+ | `runs.create(repo_url, repo_ref, payload)` | Create a new run |
77
+ | `runs.get(run_id)` | Fetch a run by ID |
78
+ | `runs.list(limit, offset, status)` | List runs |
79
+ | `runs.wait(run_id, timeout_s, poll_s, terminal_statuses)` | Poll until terminal status |
80
+
81
+ ### `Run` object
82
+
83
+ | Attribute | Type | Description |
84
+ |-----------|------|-------------|
85
+ | `.id` | `str` | Run UUID |
86
+ | `.status` | `str` | Current status (`running`, `completed`, `failed`, etc.) |
87
+ | `.output` | `str` | Output text from the run |
88
+ | `.summary` | `str \| None` | Short summary |
89
+ | `.pr_url` | `str \| None` | Pull request URL if one was created |
90
+ | `.pr_number` | `int \| None` | Pull request number |
91
+ | `.is_success()` | `bool` | `True` when status is `completed` or `succeeded` |
92
+
93
+ ## Error Handling
94
+
95
+ ```python
96
+ from tasksmind.exceptions import AuthError, NotFoundError, APIError, TimeoutError
97
+
98
+ try:
99
+ result = client.runs.wait(run_id, timeout_s=120)
100
+ except TimeoutError:
101
+ print("Run did not finish in time")
102
+ except AuthError:
103
+ print("Invalid API key")
104
+ except APIError as e:
105
+ print(f"API error {e.status_code}: {e}")
106
+ ```
107
+
108
+ ## Context Manager
109
+
110
+ ```python
111
+ with TasksMind() as client:
112
+ run = client.runs.create(repo_url="https://github.com/org/repo")
113
+ ```
114
+
115
+ ## Requirements
116
+
117
+ - Python 3.9+
118
+ - `httpx >= 0.24`
119
+
120
+ ## Links
121
+
122
+ - [Website](https://tasksmind.com)
123
+ - [Documentation](https://docs.tasksmind.com)
124
+ - [GitHub](https://github.com/tasksmind/tasksmind-python)
@@ -0,0 +1,100 @@
1
+ # TasksMind Python SDK
2
+
3
+ The official Python client for the [TasksMind API](https://tasksmind.com) — The AI Engineer for Developers on Call.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install tasksmind
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ import os
15
+ from tasksmind import TasksMind
16
+
17
+ client = TasksMind(api_key=os.environ["TASKSMIND_API_KEY"])
18
+
19
+ # Kick off a PR review
20
+ run = client.runs.create(
21
+ repo_url="https://github.com/my-org/my-repo",
22
+ repo_ref="main",
23
+ payload={
24
+ "intent": "review_pr",
25
+ "target": {"pr_number": 42},
26
+ },
27
+ )
28
+ print(f"Run started: {run.id}")
29
+
30
+ # Poll until complete (up to 10 minutes)
31
+ result = client.runs.wait(run.id, timeout_s=600)
32
+ if result.is_success():
33
+ print(result.output)
34
+ else:
35
+ print(f"Run failed with status: {result.status}")
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ | Parameter | Env variable | Default |
41
+ |-----------|-------------|---------|
42
+ | `api_key` | `TASKSMIND_API_KEY` | — (required) |
43
+ | `base_url` | `TASKSMIND_API_BASE_URL` | `https://api.tasksmind.com` |
44
+ | `timeout` | — | `60` seconds |
45
+
46
+ ## Available Resources
47
+
48
+ ### `client.runs`
49
+
50
+ | Method | Description |
51
+ |--------|-------------|
52
+ | `runs.create(repo_url, repo_ref, payload)` | Create a new run |
53
+ | `runs.get(run_id)` | Fetch a run by ID |
54
+ | `runs.list(limit, offset, status)` | List runs |
55
+ | `runs.wait(run_id, timeout_s, poll_s, terminal_statuses)` | Poll until terminal status |
56
+
57
+ ### `Run` object
58
+
59
+ | Attribute | Type | Description |
60
+ |-----------|------|-------------|
61
+ | `.id` | `str` | Run UUID |
62
+ | `.status` | `str` | Current status (`running`, `completed`, `failed`, etc.) |
63
+ | `.output` | `str` | Output text from the run |
64
+ | `.summary` | `str \| None` | Short summary |
65
+ | `.pr_url` | `str \| None` | Pull request URL if one was created |
66
+ | `.pr_number` | `int \| None` | Pull request number |
67
+ | `.is_success()` | `bool` | `True` when status is `completed` or `succeeded` |
68
+
69
+ ## Error Handling
70
+
71
+ ```python
72
+ from tasksmind.exceptions import AuthError, NotFoundError, APIError, TimeoutError
73
+
74
+ try:
75
+ result = client.runs.wait(run_id, timeout_s=120)
76
+ except TimeoutError:
77
+ print("Run did not finish in time")
78
+ except AuthError:
79
+ print("Invalid API key")
80
+ except APIError as e:
81
+ print(f"API error {e.status_code}: {e}")
82
+ ```
83
+
84
+ ## Context Manager
85
+
86
+ ```python
87
+ with TasksMind() as client:
88
+ run = client.runs.create(repo_url="https://github.com/org/repo")
89
+ ```
90
+
91
+ ## Requirements
92
+
93
+ - Python 3.9+
94
+ - `httpx >= 0.24`
95
+
96
+ ## Links
97
+
98
+ - [Website](https://tasksmind.com)
99
+ - [Documentation](https://docs.tasksmind.com)
100
+ - [GitHub](https://github.com/tasksmind/tasksmind-python)
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tasksmind"
7
+ version = "0.1.0"
8
+ description = "TasksMind Python SDK — The AI Engineer for Developers on Call."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "TasksMind", email = "support@tasksmind.com" }]
13
+ keywords = ["tasksmind", "ai", "devops", "oncall", "automation"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "httpx>=0.24.0",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://tasksmind.com"
31
+ Documentation = "https://docs.tasksmind.com"
32
+ Repository = "https://github.com/tasksmind/tasksmind-python"
33
+ "Bug Tracker" = "https://github.com/tasksmind/tasksmind-python/issues"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["tasksmind"]
@@ -0,0 +1,96 @@
1
+ """
2
+ TasksMind Python SDK
3
+ ====================
4
+
5
+ The AI Engineer for Developers on Call.
6
+
7
+ Quick start::
8
+
9
+ import os
10
+ from tasksmind import TasksMind
11
+
12
+ client = TasksMind(api_key=os.environ["TASKSMIND_API_KEY"])
13
+
14
+ # Create a run
15
+ run = client.runs.create(
16
+ repo_url="https://github.com/my-org/my-repo",
17
+ repo_ref="main",
18
+ payload={"intent": "review_pr", "target": {"pr_number": 42}},
19
+ )
20
+
21
+ # Wait for it to finish
22
+ result = client.runs.wait(run.id, timeout_s=600)
23
+ if result.is_success():
24
+ print(result.output)
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import os
30
+ from typing import Optional
31
+
32
+ from ._http import HTTPClient, _DEFAULT_BASE_URL, _DEFAULT_TIMEOUT
33
+ from .exceptions import APIError, AuthError, NotFoundError, RateLimitError, TasksMindError, TimeoutError
34
+ from .resources import Run, RunsResource
35
+
36
+ __version__ = "0.1.0"
37
+ __all__ = [
38
+ "TasksMind",
39
+ "Run",
40
+ # Exceptions
41
+ "TasksMindError",
42
+ "AuthError",
43
+ "NotFoundError",
44
+ "APIError",
45
+ "RateLimitError",
46
+ "TimeoutError",
47
+ ]
48
+
49
+
50
+ class TasksMind:
51
+ """
52
+ TasksMind API client.
53
+
54
+ Args:
55
+ api_key: Your TasksMind API key. Falls back to the ``TASKSMIND_API_KEY``
56
+ environment variable when not provided.
57
+ base_url: Override the API base URL. Defaults to ``https://api.tasksmind.com``.
58
+ Can also be set via the ``TASKSMIND_API_BASE_URL`` environment variable.
59
+ timeout: HTTP request timeout in seconds (default: 60).
60
+
61
+ Raises:
62
+ :exc:`ValueError`: If no API key is provided and ``TASKSMIND_API_KEY`` is not set.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ api_key: Optional[str] = None,
68
+ *,
69
+ base_url: Optional[str] = None,
70
+ timeout: float = _DEFAULT_TIMEOUT,
71
+ ) -> None:
72
+ resolved_key = api_key or os.environ.get("TASKSMIND_API_KEY", "")
73
+ if not resolved_key:
74
+ raise ValueError(
75
+ "No API key provided. Pass api_key= or set the TASKSMIND_API_KEY environment variable."
76
+ )
77
+ resolved_base = (
78
+ base_url
79
+ or os.environ.get("TASKSMIND_API_BASE_URL", "")
80
+ or _DEFAULT_BASE_URL
81
+ )
82
+ self._http = HTTPClient(api_key=resolved_key, base_url=resolved_base, timeout=timeout)
83
+ self.runs = RunsResource(self._http)
84
+
85
+ def close(self) -> None:
86
+ """Close the underlying HTTP connection pool."""
87
+ self._http.close()
88
+
89
+ def __enter__(self) -> "TasksMind":
90
+ return self
91
+
92
+ def __exit__(self, *args: object) -> None:
93
+ self.close()
94
+
95
+ def __repr__(self) -> str:
96
+ return f"TasksMind(base_url={self._http._base_url!r})"
@@ -0,0 +1,62 @@
1
+ """Internal HTTP client for the TasksMind SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ import httpx
8
+
9
+ from .exceptions import APIError, AuthError, NotFoundError, RateLimitError
10
+
11
+ _DEFAULT_BASE_URL = "https://api.tasksmind.com"
12
+ _DEFAULT_TIMEOUT = 60.0
13
+
14
+
15
+ class HTTPClient:
16
+ """Thin wrapper around ``httpx.Client`` that handles auth and error mapping."""
17
+
18
+ def __init__(self, api_key: str, base_url: str, timeout: float) -> None:
19
+ self._api_key = api_key
20
+ self._base_url = base_url.rstrip("/")
21
+ self._client = httpx.Client(timeout=timeout)
22
+
23
+ def _headers(self) -> Dict[str, str]:
24
+ return {
25
+ "Authorization": f"Bearer {self._api_key}",
26
+ "Content-Type": "application/json",
27
+ "Accept": "application/json",
28
+ }
29
+
30
+ def _raise_for_status(self, resp: httpx.Response) -> None:
31
+ if resp.is_success:
32
+ return
33
+ body = resp.text[:2000]
34
+ code = resp.status_code
35
+ if code in (401, 403):
36
+ raise AuthError(f"Authentication failed (HTTP {code})", status_code=code, body=body)
37
+ if code == 404:
38
+ raise NotFoundError(f"Resource not found (HTTP 404)", status_code=code, body=body)
39
+ if code == 429:
40
+ raise RateLimitError(f"Rate limit exceeded (HTTP 429)", status_code=code, body=body)
41
+ raise APIError(f"API error (HTTP {code}): {body}", status_code=code, body=body)
42
+
43
+ def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
44
+ url = self._base_url + path
45
+ resp = self._client.get(url, headers=self._headers(), params=params)
46
+ self._raise_for_status(resp)
47
+ return resp.json() if resp.text else {}
48
+
49
+ def post(self, path: str, body: Any = None) -> Any:
50
+ url = self._base_url + path
51
+ resp = self._client.post(url, headers=self._headers(), json=body)
52
+ self._raise_for_status(resp)
53
+ return resp.json() if resp.text else {}
54
+
55
+ def close(self) -> None:
56
+ self._client.close()
57
+
58
+ def __enter__(self) -> "HTTPClient":
59
+ return self
60
+
61
+ def __exit__(self, *args: Any) -> None:
62
+ self.close()
@@ -0,0 +1,35 @@
1
+ """TasksMind SDK exceptions."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class TasksMindError(Exception):
7
+ """Base exception for all TasksMind SDK errors."""
8
+
9
+ def __init__(self, message: str, status_code: int = 0, body: str = "") -> None:
10
+ super().__init__(message)
11
+ self.status_code = status_code
12
+ self.body = body
13
+
14
+ def __repr__(self) -> str:
15
+ return f"{type(self).__name__}({self!s}, status_code={self.status_code})"
16
+
17
+
18
+ class AuthError(TasksMindError):
19
+ """Raised when the API key is missing, invalid, or expired (HTTP 401/403)."""
20
+
21
+
22
+ class NotFoundError(TasksMindError):
23
+ """Raised when the requested resource does not exist (HTTP 404)."""
24
+
25
+
26
+ class RateLimitError(TasksMindError):
27
+ """Raised when the API rate limit is exceeded (HTTP 429)."""
28
+
29
+
30
+ class APIError(TasksMindError):
31
+ """Raised for all other API-level errors (HTTP 4xx/5xx)."""
32
+
33
+
34
+ class TimeoutError(TasksMindError):
35
+ """Raised when a polling call (e.g. runs.wait) exceeds its timeout."""
@@ -0,0 +1,3 @@
1
+ from .runs import Run, RunsResource
2
+
3
+ __all__ = ["Run", "RunsResource"]
@@ -0,0 +1,171 @@
1
+ """TasksMind runs resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
7
+
8
+ if TYPE_CHECKING:
9
+ from .._http import HTTPClient
10
+
11
+
12
+ class Run:
13
+ """Represents a TasksMind run."""
14
+
15
+ def __init__(self, data: Dict[str, Any]) -> None:
16
+ self._data = data
17
+
18
+ @property
19
+ def id(self) -> str:
20
+ return self._data.get("id", "")
21
+
22
+ @property
23
+ def status(self) -> str:
24
+ return self._data.get("status", "unknown")
25
+
26
+ @property
27
+ def output(self) -> str:
28
+ return self._data.get("output") or ""
29
+
30
+ @property
31
+ def pr_url(self) -> Optional[str]:
32
+ return self._data.get("pr_url")
33
+
34
+ @property
35
+ def pr_number(self) -> Optional[int]:
36
+ return self._data.get("pr_number")
37
+
38
+ @property
39
+ def summary(self) -> Optional[str]:
40
+ return self._data.get("summary")
41
+
42
+ @property
43
+ def error(self) -> Optional[str]:
44
+ return self._data.get("error")
45
+
46
+ def is_success(self) -> bool:
47
+ return self.status in ("completed", "succeeded")
48
+
49
+ def __repr__(self) -> str:
50
+ return f"Run(id={self.id!r}, status={self.status!r})"
51
+
52
+ def __getitem__(self, key: str) -> Any:
53
+ return self._data[key]
54
+
55
+ def get(self, key: str, default: Any = None) -> Any:
56
+ return self._data.get(key, default)
57
+
58
+
59
+ class RunsResource:
60
+ """CRUD and polling for TasksMind runs."""
61
+
62
+ def __init__(self, http: "HTTPClient") -> None:
63
+ self._http = http
64
+
65
+ def create(
66
+ self,
67
+ *,
68
+ repo_url: str,
69
+ repo_ref: str = "main",
70
+ payload: Optional[Dict[str, Any]] = None,
71
+ ) -> Run:
72
+ """
73
+ Create a new run.
74
+
75
+ Args:
76
+ repo_url: Full GitHub repository URL (e.g. ``https://github.com/org/repo``).
77
+ repo_ref: Branch, tag, or commit SHA to target (default: ``"main"``).
78
+ payload: Arbitrary key/value context forwarded to the run (intent, target, etc.).
79
+
80
+ Returns:
81
+ A :class:`Run` object with at minimum an ``id`` attribute.
82
+ """
83
+ body: Dict[str, Any] = {
84
+ "repo_url": repo_url,
85
+ "repo_ref": repo_ref,
86
+ }
87
+ if payload:
88
+ body["payload"] = payload
89
+ data = self._http.post("/v1/runs", body)
90
+ return Run(data)
91
+
92
+ def get(self, run_id: str) -> Run:
93
+ """
94
+ Fetch an existing run by its ID.
95
+
96
+ Args:
97
+ run_id: The UUID of the run.
98
+
99
+ Returns:
100
+ A :class:`Run` object.
101
+
102
+ Raises:
103
+ :exc:`~tasksmind.exceptions.NotFoundError`: If the run does not exist.
104
+ """
105
+ data = self._http.get(f"/v1/runs/{run_id}")
106
+ return Run(data)
107
+
108
+ def list(
109
+ self,
110
+ *,
111
+ limit: int = 20,
112
+ offset: int = 0,
113
+ status: Optional[str] = None,
114
+ ) -> List[Run]:
115
+ """
116
+ List runs for the authenticated account.
117
+
118
+ Args:
119
+ limit: Maximum number of runs to return (default: 20).
120
+ offset: Pagination offset (default: 0).
121
+ status: Optional status filter (e.g. ``"completed"``, ``"running"``).
122
+
123
+ Returns:
124
+ A list of :class:`Run` objects.
125
+ """
126
+ params: Dict[str, Any] = {"limit": limit, "offset": offset}
127
+ if status:
128
+ params["status"] = status
129
+ data = self._http.get("/v1/runs", params=params)
130
+ return [Run(r) for r in (data.get("runs") or data if isinstance(data, list) else [])]
131
+
132
+ def wait(
133
+ self,
134
+ run_id: str,
135
+ *,
136
+ timeout_s: float = 600.0,
137
+ poll_s: float = 2.0,
138
+ terminal_statuses: Optional[List[str]] = None,
139
+ ) -> Run:
140
+ """
141
+ Poll a run until it reaches a terminal status or the timeout expires.
142
+
143
+ Args:
144
+ run_id: The UUID of the run to poll.
145
+ timeout_s: Maximum seconds to wait before raising :exc:`~tasksmind.exceptions.TimeoutError`.
146
+ poll_s: Seconds between each poll request (default: 2).
147
+ terminal_statuses: Status strings that stop polling. Defaults to
148
+ ``["completed", "succeeded", "failed", "error", "cancelled"]``.
149
+
150
+ Returns:
151
+ The final :class:`Run` object.
152
+
153
+ Raises:
154
+ :exc:`~tasksmind.exceptions.TimeoutError`: If the run does not finish within ``timeout_s``.
155
+ """
156
+ from ..exceptions import TimeoutError as TasksMindTimeoutError
157
+
158
+ if terminal_statuses is None:
159
+ terminal_statuses = ["completed", "succeeded", "failed", "error", "cancelled"]
160
+
161
+ deadline = time.monotonic() + timeout_s
162
+ while True:
163
+ run = self.get(run_id)
164
+ if run.status in terminal_statuses:
165
+ return run
166
+ remaining = deadline - time.monotonic()
167
+ if remaining <= 0:
168
+ raise TasksMindTimeoutError(
169
+ f"Run {run_id!r} did not complete within {timeout_s}s (last status: {run.status!r})"
170
+ )
171
+ time.sleep(min(poll_s, remaining))