taskswarm-cli 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.
Files changed (48) hide show
  1. taskswarm_cli-0.1.0/.gitignore +8 -0
  2. taskswarm_cli-0.1.0/LICENSE +21 -0
  3. taskswarm_cli-0.1.0/PKG-INFO +217 -0
  4. taskswarm_cli-0.1.0/README.md +182 -0
  5. taskswarm_cli-0.1.0/examples/01-basic-server/run.py +64 -0
  6. taskswarm_cli-0.1.0/examples/02-ci-gate/gate.py +70 -0
  7. taskswarm_cli-0.1.0/examples/03-claude-code-hook/relay.py +73 -0
  8. taskswarm_cli-0.1.0/examples/README.md +29 -0
  9. taskswarm_cli-0.1.0/pyproject.toml +67 -0
  10. taskswarm_cli-0.1.0/src/taskswarm/__init__.py +67 -0
  11. taskswarm_cli-0.1.0/src/taskswarm/adapters/__init__.py +11 -0
  12. taskswarm_cli-0.1.0/src/taskswarm/adapters/claude_code_adapter.py +226 -0
  13. taskswarm_cli-0.1.0/src/taskswarm/adapters/generic_adapter.py +43 -0
  14. taskswarm_cli-0.1.0/src/taskswarm/adapters/types.py +23 -0
  15. taskswarm_cli-0.1.0/src/taskswarm/cli.py +323 -0
  16. taskswarm_cli-0.1.0/src/taskswarm/client/__init__.py +0 -0
  17. taskswarm_cli-0.1.0/src/taskswarm/client/api_client.py +61 -0
  18. taskswarm_cli-0.1.0/src/taskswarm/client/tasks_registry.py +119 -0
  19. taskswarm_cli-0.1.0/src/taskswarm/notifications/__init__.py +3 -0
  20. taskswarm_cli-0.1.0/src/taskswarm/notifications/dispatch.py +88 -0
  21. taskswarm_cli-0.1.0/src/taskswarm/notifications/ntfy.py +61 -0
  22. taskswarm_cli-0.1.0/src/taskswarm/notifications/os_notify.py +61 -0
  23. taskswarm_cli-0.1.0/src/taskswarm/py.typed +0 -0
  24. taskswarm_cli-0.1.0/src/taskswarm/schema/__init__.py +23 -0
  25. taskswarm_cli-0.1.0/src/taskswarm/schema/events.py +221 -0
  26. taskswarm_cli-0.1.0/src/taskswarm/server/__init__.py +33 -0
  27. taskswarm_cli-0.1.0/src/taskswarm/server/auth.py +25 -0
  28. taskswarm_cli-0.1.0/src/taskswarm/server/config.py +154 -0
  29. taskswarm_cli-0.1.0/src/taskswarm/server/event_store.py +139 -0
  30. taskswarm_cli-0.1.0/src/taskswarm/server/http_server.py +255 -0
  31. taskswarm_cli-0.1.0/src/taskswarm/server/server.py +72 -0
  32. taskswarm_cli-0.1.0/src/taskswarm/ui/index.html +241 -0
  33. taskswarm_cli-0.1.0/src/taskswarm/util/__init__.py +0 -0
  34. taskswarm_cli-0.1.0/src/taskswarm/util/sync_sleep.py +15 -0
  35. taskswarm_cli-0.1.0/tests/__init__.py +0 -0
  36. taskswarm_cli-0.1.0/tests/conftest.py +14 -0
  37. taskswarm_cli-0.1.0/tests/test_adapters.py +200 -0
  38. taskswarm_cli-0.1.0/tests/test_api_client.py +80 -0
  39. taskswarm_cli-0.1.0/tests/test_auth.py +33 -0
  40. taskswarm_cli-0.1.0/tests/test_cli.py +138 -0
  41. taskswarm_cli-0.1.0/tests/test_config.py +110 -0
  42. taskswarm_cli-0.1.0/tests/test_event_store.py +112 -0
  43. taskswarm_cli-0.1.0/tests/test_events_schema.py +147 -0
  44. taskswarm_cli-0.1.0/tests/test_notify.py +103 -0
  45. taskswarm_cli-0.1.0/tests/test_ntfy.py +52 -0
  46. taskswarm_cli-0.1.0/tests/test_os_notify.py +20 -0
  47. taskswarm_cli-0.1.0/tests/test_server_lifecycle.py +179 -0
  48. taskswarm_cli-0.1.0/tests/test_tasks_registry.py +72 -0
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ .venv-freshinstall/
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ __pycache__/
7
+ *.pyc
8
+ .pytest_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rudrendu Paul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: taskswarm-cli
3
+ Version: 0.1.0
4
+ Summary: Self-hosted event server that pushes a notification the instant a parallel coding-agent session blocks, needs review, fails, or finishes.
5
+ Project-URL: Homepage, https://github.com/RudrenduPaul/taskswarm
6
+ Project-URL: Repository, https://github.com/RudrenduPaul/taskswarm
7
+ Project-URL: Bug Tracker, https://github.com/RudrenduPaul/taskswarm/issues
8
+ Project-URL: Changelog, https://github.com/RudrenduPaul/taskswarm/blob/main/CHANGELOG.md
9
+ Project-URL: Documentation, https://github.com/RudrenduPaul/taskswarm/blob/main/docs/getting-started.md
10
+ Project-URL: Author - Rudrendu Paul, https://github.com/RudrenduPaul
11
+ Project-URL: Author - Sourav Nandy, https://github.com/Sourav-nandy-ai
12
+ Author: Rudrendu Paul, Sourav Nandy
13
+ License-Expression: MIT
14
+ License-File: LICENSE
15
+ Keywords: claude-code,cli,coding-agent,dashboard,event-server,multi-agent,notifications,orchestration,self-hosted
16
+ Classifier: Development Status :: 3 - Alpha
17
+ Classifier: Environment :: Console
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Topic :: Software Development :: Build Tools
28
+ Classifier: Topic :: System :: Monitoring
29
+ Requires-Python: >=3.9
30
+ Provides-Extra: dev
31
+ Requires-Dist: build<2,>=1.0; extra == 'dev'
32
+ Requires-Dist: pytest<9,>=7.0; extra == 'dev'
33
+ Requires-Dist: twine<7,>=5.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # taskswarm (Python)
37
+
38
+ Self-hosted event server that pushes a notification the instant a parallel
39
+ coding-agent session blocks, needs review, fails, or finishes.
40
+
41
+ [![PyPI version](https://img.shields.io/pypi/v/taskswarm.svg)](https://pypi.org/project/taskswarm/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/RudrenduPaul/taskswarm/blob/main/LICENSE)
43
+ [![Python versions](https://img.shields.io/pypi/pyversions/taskswarm.svg)](https://pypi.org/project/taskswarm/)
44
+ [![CI](https://github.com/RudrenduPaul/taskswarm/actions/workflows/ci.yml/badge.svg)](https://github.com/RudrenduPaul/taskswarm/actions/workflows/ci.yml)
45
+
46
+ ## Why this exists
47
+
48
+ Running three or four coding-agent sessions in parallel turns into a
49
+ tab-switching problem: nothing pushes state to you, so you find out a
50
+ session has been sitting on a permission prompt for ten minutes only by
51
+ polling terminals by eye. TaskSwarm closes that gap: every agent session
52
+ reports its state to a small local HTTP server, and the instant one
53
+ transitions to `blocked`, `needs-review`, `failed`, or `done`, TaskSwarm
54
+ fires a local OS notification and updates a live status page over
55
+ Server-Sent Events. No polling, no account, no cloud dependency by default.
56
+
57
+ This package is the **Python distribution** of TaskSwarm -- a genuine,
58
+ independent port of the server, CLI, event schema, and notification logic,
59
+ not a wrapper around the Node binary. It has zero runtime dependencies: no
60
+ HTTP framework, no schema-validation library, nothing beyond the Python
61
+ standard library.
62
+
63
+ ## Install
64
+
65
+ Live on PyPI as `taskswarm`:
66
+
67
+ ```bash
68
+ pip install taskswarm
69
+ ```
70
+
71
+ or with [uv](https://docs.astral.sh/uv/):
72
+
73
+ ```bash
74
+ uv add taskswarm
75
+ ```
76
+
77
+ The complementary JS/TS distribution is already live on npm as
78
+ `taskswarm-cli` (`npm install -g taskswarm-cli`, or `npx taskswarm-cli
79
+ start` with no install step) -- see the
80
+ [project README](https://github.com/RudrenduPaul/taskswarm#readme) for
81
+ that package. Both are first-class and maintained together; neither is a
82
+ replacement for the other.
83
+
84
+ ## Quickstart
85
+
86
+ ```bash
87
+ # Terminal 1: start the server
88
+ taskswarm start
89
+ # TaskSwarm server listening on http://127.0.0.1:4173
90
+ # Live status page: http://127.0.0.1:4173/?token=<your-token>
91
+
92
+ # Terminal 2: report a session's status as it works
93
+ taskswarm agent report-status --task my-fix --repo ./api --state running
94
+ taskswarm agent report-status --task my-fix --repo ./api --state done
95
+ ```
96
+
97
+ Open the live status page URL printed by `start`. The row for `my-fix`
98
+ updates the instant each `report-status` call lands, no refresh.
99
+
100
+ Or call the library directly, in-process, without a subprocess:
101
+
102
+ ```python
103
+ from taskswarm import start_server
104
+ from taskswarm.adapters import GenericAdapter
105
+ from taskswarm.client.api_client import post_event
106
+
107
+ running = start_server()
108
+ adapter = GenericAdapter()
109
+ event_input = adapter.to_event_input(
110
+ {"session_id": "my-fix", "repo": "./api", "status": "done", "agent_type": "generic"}
111
+ )
112
+ event = post_event(running.config.to_dict(), event_input)
113
+ print(event["session_id"], "->", event["status"])
114
+ running.close()
115
+ ```
116
+
117
+ ## What it does
118
+
119
+ - **Event server, not a board you have to keep open.** `taskswarm start`
120
+ boots an HTTP+SSE server (stdlib `http.server`, no framework dependency)
121
+ that accepts `POST /events`, serves `GET /events` for current session
122
+ state, and streams new events over `GET /live` (Server-Sent Events) to
123
+ the bundled live status page.
124
+ - **Push notification on the four states that mean "look at this now."**
125
+ The moment a session's status transitions to `blocked`, `needs-review`,
126
+ `failed`, or `done`, TaskSwarm fires a native OS notification
127
+ (`osascript` on macOS, a terminal-bell console fallback elsewhere).
128
+ Notification dedup keys on the `(status, blocked_reason)` pair, so a
129
+ second, different permission prompt still notifies even though the
130
+ status (`needs-review`) didn't change.
131
+ - **A wrapper-script adapter for any agent.**
132
+ `taskswarm agent report-status --task <id> --repo <path> --state <state>`
133
+ is the one primitive every integration is built on -- callable from any
134
+ script wrapping any CLI agent (Codex, Cursor, or anything else).
135
+ - **A Claude Code hooks adapter**, ported with the same behavior as the npm
136
+ package's: `taskswarm hooks install claude-code` writes `Stop` and
137
+ `Notification` hook entries into `.claude/settings.json`, pointed at the
138
+ exact, already-installed console script on disk (never a floating PATH
139
+ lookup re-resolved on every hook fire).
140
+ - **A bearer-token-gated local API, bound to loopback by default.**
141
+ `POST /events` and the live page both require the token TaskSwarm
142
+ generates on first run (`~/.taskswarm/config.json`, written `0600`).
143
+ Rotate it with `taskswarm token rotate`.
144
+ - **Agent-native by design.** Every subcommand ships a `--json` flag with a
145
+ stable schema, including error output.
146
+ - **ntfy.sh is opt-in, never default.** The only notification channel that
147
+ leaves your machine, and it's off unless you configure it in
148
+ `~/.taskswarm/config.json`.
149
+
150
+ ## CLI reference
151
+
152
+ | Command | Description | Key options |
153
+ | ----------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
154
+ | `taskswarm start` | Start the TaskSwarm server and print the live status page URL | `--json` |
155
+ | `taskswarm task add` | Register a new task | `--title <title>` (required), `--repo <path>` (required), `--json` |
156
+ | `taskswarm task list` | List tracked tasks, enriched with live status when the server is reachable | `--json` |
157
+ | `taskswarm agent report-status` | Report a status transition for a task/session to the local server | `--task <id>` (required), `--repo <path>` (required), `--state <state>` (required, one of `queued\|running\|blocked\|needs-review\|done\|failed`), `--blocked-reason <text>`, `--agent-type <type>` (`claude-code\|codex\|cursor\|generic`, default `generic`), `--json` |
158
+ | `taskswarm token rotate` | Generate a new bearer token, invalidating the old one | `--json` |
159
+ | `taskswarm hooks install <adapter>` | Install hooks for an agent integration (currently `claude-code`) | `--scope <project\|local\|user>` (default `project`), `--project-dir <path>`, `--json` |
160
+ | `taskswarm hooks claude-code-relay` | Internal: reads a hook payload from stdin and relays it. Installed automatically; not meant to be run by hand. | none |
161
+
162
+ `taskswarm --version` prints `taskswarm 0.1.0`. The `taskswarm-cli` console
163
+ script installed by this package is an identical alias, matching both `bin`
164
+ entries the npm package ships.
165
+
166
+ ## How it works
167
+
168
+ ```
169
+ agent session / hook / wrapper script
170
+ |
171
+ v
172
+ POST /events (bearer token required) -> event schema validation
173
+ |
174
+ v
175
+ EventStore (in-memory + append-only JSONL log at ~/.taskswarm/events.jsonl)
176
+ |
177
+ +--> notify() -- fires on a transition into blocked/needs-review/failed/done
178
+ | |
179
+ | +--> local OS notification (always on)
180
+ | +--> ntfy.sh (opt-in only)
181
+ |
182
+ +--> GET /live (Server-Sent Events) -> live status page
183
+ ```
184
+
185
+ Full data model and the exact notification-dedup rule are in
186
+ [docs/concepts.md](https://github.com/RudrenduPaul/taskswarm/blob/main/docs/concepts.md).
187
+
188
+ ## Security
189
+
190
+ The local API is gated by a bearer token generated on first run and stored
191
+ `0600` at `~/.taskswarm/config.json`; the server binds to `127.0.0.1` by
192
+ default. Token comparison uses `hmac.compare_digest` (constant-time,
193
+ avoiding timing side-channels), the same property the TypeScript version
194
+ gets from `crypto.timingSafeEqual`. See
195
+ [SECURITY.md](https://github.com/RudrenduPaul/taskswarm/blob/main/SECURITY.md)
196
+ for the full posture, including what this server does **not** protect
197
+ against (it is a local developer tool, not designed to be exposed on a
198
+ shared or public network). **Honest note**: this project does not currently
199
+ publish SLSA provenance, Sigstore signatures, or an SBOM, and has no
200
+ OpenSSF Scorecard badge -- none of that infrastructure exists yet for
201
+ either distribution, so it isn't claimed here.
202
+
203
+ ## Contributing
204
+
205
+ See [CONTRIBUTING.md](https://github.com/RudrenduPaul/taskswarm/blob/main/CONTRIBUTING.md)
206
+ for the full guide, covering both the TypeScript and Python codebases.
207
+
208
+ ```bash
209
+ cd python
210
+ python3 -m venv .venv && source .venv/bin/activate
211
+ pip install -e ".[dev]"
212
+ pytest
213
+ ```
214
+
215
+ ## License
216
+
217
+ MIT, see [LICENSE](https://github.com/RudrenduPaul/taskswarm/blob/main/LICENSE).
@@ -0,0 +1,182 @@
1
+ # taskswarm (Python)
2
+
3
+ Self-hosted event server that pushes a notification the instant a parallel
4
+ coding-agent session blocks, needs review, fails, or finishes.
5
+
6
+ [![PyPI version](https://img.shields.io/pypi/v/taskswarm.svg)](https://pypi.org/project/taskswarm/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/RudrenduPaul/taskswarm/blob/main/LICENSE)
8
+ [![Python versions](https://img.shields.io/pypi/pyversions/taskswarm.svg)](https://pypi.org/project/taskswarm/)
9
+ [![CI](https://github.com/RudrenduPaul/taskswarm/actions/workflows/ci.yml/badge.svg)](https://github.com/RudrenduPaul/taskswarm/actions/workflows/ci.yml)
10
+
11
+ ## Why this exists
12
+
13
+ Running three or four coding-agent sessions in parallel turns into a
14
+ tab-switching problem: nothing pushes state to you, so you find out a
15
+ session has been sitting on a permission prompt for ten minutes only by
16
+ polling terminals by eye. TaskSwarm closes that gap: every agent session
17
+ reports its state to a small local HTTP server, and the instant one
18
+ transitions to `blocked`, `needs-review`, `failed`, or `done`, TaskSwarm
19
+ fires a local OS notification and updates a live status page over
20
+ Server-Sent Events. No polling, no account, no cloud dependency by default.
21
+
22
+ This package is the **Python distribution** of TaskSwarm -- a genuine,
23
+ independent port of the server, CLI, event schema, and notification logic,
24
+ not a wrapper around the Node binary. It has zero runtime dependencies: no
25
+ HTTP framework, no schema-validation library, nothing beyond the Python
26
+ standard library.
27
+
28
+ ## Install
29
+
30
+ Live on PyPI as `taskswarm`:
31
+
32
+ ```bash
33
+ pip install taskswarm
34
+ ```
35
+
36
+ or with [uv](https://docs.astral.sh/uv/):
37
+
38
+ ```bash
39
+ uv add taskswarm
40
+ ```
41
+
42
+ The complementary JS/TS distribution is already live on npm as
43
+ `taskswarm-cli` (`npm install -g taskswarm-cli`, or `npx taskswarm-cli
44
+ start` with no install step) -- see the
45
+ [project README](https://github.com/RudrenduPaul/taskswarm#readme) for
46
+ that package. Both are first-class and maintained together; neither is a
47
+ replacement for the other.
48
+
49
+ ## Quickstart
50
+
51
+ ```bash
52
+ # Terminal 1: start the server
53
+ taskswarm start
54
+ # TaskSwarm server listening on http://127.0.0.1:4173
55
+ # Live status page: http://127.0.0.1:4173/?token=<your-token>
56
+
57
+ # Terminal 2: report a session's status as it works
58
+ taskswarm agent report-status --task my-fix --repo ./api --state running
59
+ taskswarm agent report-status --task my-fix --repo ./api --state done
60
+ ```
61
+
62
+ Open the live status page URL printed by `start`. The row for `my-fix`
63
+ updates the instant each `report-status` call lands, no refresh.
64
+
65
+ Or call the library directly, in-process, without a subprocess:
66
+
67
+ ```python
68
+ from taskswarm import start_server
69
+ from taskswarm.adapters import GenericAdapter
70
+ from taskswarm.client.api_client import post_event
71
+
72
+ running = start_server()
73
+ adapter = GenericAdapter()
74
+ event_input = adapter.to_event_input(
75
+ {"session_id": "my-fix", "repo": "./api", "status": "done", "agent_type": "generic"}
76
+ )
77
+ event = post_event(running.config.to_dict(), event_input)
78
+ print(event["session_id"], "->", event["status"])
79
+ running.close()
80
+ ```
81
+
82
+ ## What it does
83
+
84
+ - **Event server, not a board you have to keep open.** `taskswarm start`
85
+ boots an HTTP+SSE server (stdlib `http.server`, no framework dependency)
86
+ that accepts `POST /events`, serves `GET /events` for current session
87
+ state, and streams new events over `GET /live` (Server-Sent Events) to
88
+ the bundled live status page.
89
+ - **Push notification on the four states that mean "look at this now."**
90
+ The moment a session's status transitions to `blocked`, `needs-review`,
91
+ `failed`, or `done`, TaskSwarm fires a native OS notification
92
+ (`osascript` on macOS, a terminal-bell console fallback elsewhere).
93
+ Notification dedup keys on the `(status, blocked_reason)` pair, so a
94
+ second, different permission prompt still notifies even though the
95
+ status (`needs-review`) didn't change.
96
+ - **A wrapper-script adapter for any agent.**
97
+ `taskswarm agent report-status --task <id> --repo <path> --state <state>`
98
+ is the one primitive every integration is built on -- callable from any
99
+ script wrapping any CLI agent (Codex, Cursor, or anything else).
100
+ - **A Claude Code hooks adapter**, ported with the same behavior as the npm
101
+ package's: `taskswarm hooks install claude-code` writes `Stop` and
102
+ `Notification` hook entries into `.claude/settings.json`, pointed at the
103
+ exact, already-installed console script on disk (never a floating PATH
104
+ lookup re-resolved on every hook fire).
105
+ - **A bearer-token-gated local API, bound to loopback by default.**
106
+ `POST /events` and the live page both require the token TaskSwarm
107
+ generates on first run (`~/.taskswarm/config.json`, written `0600`).
108
+ Rotate it with `taskswarm token rotate`.
109
+ - **Agent-native by design.** Every subcommand ships a `--json` flag with a
110
+ stable schema, including error output.
111
+ - **ntfy.sh is opt-in, never default.** The only notification channel that
112
+ leaves your machine, and it's off unless you configure it in
113
+ `~/.taskswarm/config.json`.
114
+
115
+ ## CLI reference
116
+
117
+ | Command | Description | Key options |
118
+ | ----------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
119
+ | `taskswarm start` | Start the TaskSwarm server and print the live status page URL | `--json` |
120
+ | `taskswarm task add` | Register a new task | `--title <title>` (required), `--repo <path>` (required), `--json` |
121
+ | `taskswarm task list` | List tracked tasks, enriched with live status when the server is reachable | `--json` |
122
+ | `taskswarm agent report-status` | Report a status transition for a task/session to the local server | `--task <id>` (required), `--repo <path>` (required), `--state <state>` (required, one of `queued\|running\|blocked\|needs-review\|done\|failed`), `--blocked-reason <text>`, `--agent-type <type>` (`claude-code\|codex\|cursor\|generic`, default `generic`), `--json` |
123
+ | `taskswarm token rotate` | Generate a new bearer token, invalidating the old one | `--json` |
124
+ | `taskswarm hooks install <adapter>` | Install hooks for an agent integration (currently `claude-code`) | `--scope <project\|local\|user>` (default `project`), `--project-dir <path>`, `--json` |
125
+ | `taskswarm hooks claude-code-relay` | Internal: reads a hook payload from stdin and relays it. Installed automatically; not meant to be run by hand. | none |
126
+
127
+ `taskswarm --version` prints `taskswarm 0.1.0`. The `taskswarm-cli` console
128
+ script installed by this package is an identical alias, matching both `bin`
129
+ entries the npm package ships.
130
+
131
+ ## How it works
132
+
133
+ ```
134
+ agent session / hook / wrapper script
135
+ |
136
+ v
137
+ POST /events (bearer token required) -> event schema validation
138
+ |
139
+ v
140
+ EventStore (in-memory + append-only JSONL log at ~/.taskswarm/events.jsonl)
141
+ |
142
+ +--> notify() -- fires on a transition into blocked/needs-review/failed/done
143
+ | |
144
+ | +--> local OS notification (always on)
145
+ | +--> ntfy.sh (opt-in only)
146
+ |
147
+ +--> GET /live (Server-Sent Events) -> live status page
148
+ ```
149
+
150
+ Full data model and the exact notification-dedup rule are in
151
+ [docs/concepts.md](https://github.com/RudrenduPaul/taskswarm/blob/main/docs/concepts.md).
152
+
153
+ ## Security
154
+
155
+ The local API is gated by a bearer token generated on first run and stored
156
+ `0600` at `~/.taskswarm/config.json`; the server binds to `127.0.0.1` by
157
+ default. Token comparison uses `hmac.compare_digest` (constant-time,
158
+ avoiding timing side-channels), the same property the TypeScript version
159
+ gets from `crypto.timingSafeEqual`. See
160
+ [SECURITY.md](https://github.com/RudrenduPaul/taskswarm/blob/main/SECURITY.md)
161
+ for the full posture, including what this server does **not** protect
162
+ against (it is a local developer tool, not designed to be exposed on a
163
+ shared or public network). **Honest note**: this project does not currently
164
+ publish SLSA provenance, Sigstore signatures, or an SBOM, and has no
165
+ OpenSSF Scorecard badge -- none of that infrastructure exists yet for
166
+ either distribution, so it isn't claimed here.
167
+
168
+ ## Contributing
169
+
170
+ See [CONTRIBUTING.md](https://github.com/RudrenduPaul/taskswarm/blob/main/CONTRIBUTING.md)
171
+ for the full guide, covering both the TypeScript and Python codebases.
172
+
173
+ ```bash
174
+ cd python
175
+ python3 -m venv .venv && source .venv/bin/activate
176
+ pip install -e ".[dev]"
177
+ pytest
178
+ ```
179
+
180
+ ## License
181
+
182
+ MIT, see [LICENSE](https://github.com/RudrenduPaul/taskswarm/blob/main/LICENSE).
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Boots a real TaskSwarm server on an ephemeral port, reports a session
4
+ through queued -> running -> done using the GenericAdapter, reads back live
5
+ session state, and confirms a local notification fired on the transition
6
+ into 'done' (one of the four states TaskSwarm's notification layer watches
7
+ for). Run it directly:
8
+
9
+ python3 examples/01-basic-server/run.py
10
+ """
11
+ import json
12
+ import tempfile
13
+ from pathlib import Path
14
+
15
+ from taskswarm.adapters import GenericAdapter
16
+ from taskswarm.client.api_client import get_sessions, post_event
17
+ from taskswarm.notifications import NotifyOptions
18
+ from taskswarm.server.config import TaskSwarmConfig, generate_token
19
+ from taskswarm.server.server import start_server
20
+
21
+
22
+ def main() -> None:
23
+ fired = []
24
+
25
+ def on_notify(title: str, message: str) -> None:
26
+ fired.append((title, message))
27
+
28
+ # An ephemeral, isolated home dir -- this example never touches your
29
+ # real ~/.taskswarm.
30
+ home = tempfile.mkdtemp(prefix="taskswarm-example-")
31
+ print(f"Using TASKSWARM_HOME={home}")
32
+
33
+ config = TaskSwarmConfig(token=generate_token(), port=0, host="127.0.0.1")
34
+ running = start_server(
35
+ config=config,
36
+ log_path=str(Path(home) / "events.jsonl"),
37
+ notify_options=NotifyOptions(os_notifier=on_notify),
38
+ )
39
+ print(f"Server listening at {running.url}")
40
+
41
+ adapter = GenericAdapter()
42
+
43
+ for status in ("queued", "running", "done"):
44
+ event_input = adapter.to_event_input(
45
+ {"session_id": "example-task", "repo": "./demo-repo", "status": status, "agent_type": "generic"}
46
+ )
47
+ event = post_event(running.config.to_dict(), event_input)
48
+ print(f"reported: {event['session_id']} -> {event['status']}")
49
+
50
+ sessions = get_sessions(running.config.to_dict())
51
+ print("\ncurrent session state:")
52
+ print(json.dumps(sessions, indent=2))
53
+
54
+ print(f"\nnotifications fired: {len(fired)}")
55
+ for title, message in fired:
56
+ print(f" [{title}] {message}")
57
+
58
+ assert len(fired) == 1, "expected exactly one notification, for the 'done' transition"
59
+ running.close()
60
+ print("\nOK: server started, event reported, notification fired, server closed cleanly.")
61
+
62
+
63
+ if __name__ == "__main__":
64
+ main()
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Demonstrates the CI-gate pattern documented in docs/integrations/ci.md:
4
+ report a task's outcome, poll session state, and exit non-zero if anything
5
+ failed -- the shape a real CI step would use, minus the real CI system.
6
+
7
+ Boots its own ephemeral server so this is runnable standalone. In a real
8
+ pipeline, drop the `start_server()` call and point `config` at an
9
+ already-running server's `~/.taskswarm/config.json` instead (see
10
+ `taskswarm.load_or_create_config()`).
11
+
12
+ python3 examples/02-ci-gate/gate.py
13
+ """
14
+ import sys
15
+ import tempfile
16
+ from pathlib import Path
17
+
18
+ from taskswarm.adapters import GenericAdapter
19
+ from taskswarm.client.api_client import get_sessions, post_event
20
+ from taskswarm.notifications import NotifyOptions
21
+ from taskswarm.server.config import TaskSwarmConfig, generate_token
22
+ from taskswarm.server.server import start_server
23
+
24
+
25
+ def run_ci_gate(config: dict) -> int:
26
+ """Returns the process exit code a real CI step would use: 0 if no
27
+ tracked session ended in 'failed', 1 otherwise."""
28
+ sessions = get_sessions(config)
29
+ failed = [s for s in sessions if s["latest"]["status"] == "failed"]
30
+ if failed:
31
+ for session in failed:
32
+ reason = session["latest"].get("blocked_reason", "no reason given")
33
+ print(f"FAILED: {session['session_id']} ({reason})", file=sys.stderr)
34
+ return 1
35
+ print(f"{len(sessions)} session(s) tracked, none failed.")
36
+ return 0
37
+
38
+
39
+ def main() -> None:
40
+ home = tempfile.mkdtemp(prefix="taskswarm-example-")
41
+ config = TaskSwarmConfig(token=generate_token(), port=0, host="127.0.0.1")
42
+ running = start_server(
43
+ config=config,
44
+ log_path=str(Path(home) / "events.jsonl"),
45
+ notify_options=NotifyOptions(os_notifier=lambda title, message: None),
46
+ )
47
+
48
+ adapter = GenericAdapter()
49
+ # Simulate an agent task that failed partway through.
50
+ post_event(
51
+ running.config.to_dict(),
52
+ adapter.to_event_input(
53
+ {
54
+ "session_id": "flaky-test-fix",
55
+ "repo": "./demo-repo",
56
+ "status": "failed",
57
+ "agent_type": "generic",
58
+ "blocked_reason": "test suite still red after the agent's fix",
59
+ }
60
+ ),
61
+ )
62
+
63
+ exit_code = run_ci_gate(running.config.to_dict())
64
+ running.close()
65
+ print(f"\nCI gate would exit with code {exit_code}")
66
+ sys.exit(exit_code)
67
+
68
+
69
+ if __name__ == "__main__":
70
+ main()
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ The agent-native use case: feeds a real Claude Code hook payload shape
4
+ (the same JSON shape Claude Code writes to a hook's stdin) through
5
+ ClaudeCodeAdapter in-process -- no CLI subprocess, no server needed for
6
+ this part -- and inspects the resulting event input plus the
7
+ notification-dedup decision (should_notify) directly.
8
+
9
+ python3 examples/03-claude-code-hook/relay.py
10
+ """
11
+ import json
12
+
13
+ from taskswarm.adapters import ClaudeCodeAdapter
14
+ from taskswarm.notifications import should_notify
15
+
16
+ # A realistic 'Notification' hook payload -- the shape Claude Code writes
17
+ # to stdin when a permission prompt appears mid-session.
18
+ permission_prompt_payload = {
19
+ "session_id": "claude-session-42",
20
+ "cwd": "/Users/demo/projects/api",
21
+ "hook_event_name": "Notification",
22
+ "notification_type": "permission_prompt",
23
+ }
24
+
25
+ # A 'Stop' payload -- fires when Claude Code finishes responding to a turn.
26
+ stop_payload = {
27
+ "session_id": "claude-session-42",
28
+ "cwd": "/Users/demo/projects/api",
29
+ "hook_event_name": "Stop",
30
+ }
31
+
32
+
33
+ def main() -> None:
34
+ adapter = ClaudeCodeAdapter()
35
+
36
+ print("Permission-prompt hook -> event input:")
37
+ permission_event = adapter.to_event_input(permission_prompt_payload)
38
+ print(json.dumps(permission_event, indent=2))
39
+
40
+ print("\nStop hook -> event input:")
41
+ stop_event = adapter.to_event_input(stop_payload)
42
+ print(json.dumps(stop_event, indent=2))
43
+
44
+ # Notification-dedup: a first 'needs-review' with no prior status
45
+ # notifies. A second, identical-reason 'needs-review' right after does
46
+ # not (it's a repeat). A third 'needs-review' with a *different*
47
+ # blocked_reason (a different permission prompt) notifies again, even
48
+ # though the status itself hasn't changed.
49
+ print("\nNotification-dedup walkthrough:")
50
+ decisions = [
51
+ ("first permission prompt", should_notify("needs-review", None, permission_event["blocked_reason"], None)),
52
+ (
53
+ "identical repeat",
54
+ should_notify(
55
+ "needs-review", "needs-review", permission_event["blocked_reason"], permission_event["blocked_reason"]
56
+ ),
57
+ ),
58
+ (
59
+ "a second, different prompt",
60
+ should_notify("needs-review", "needs-review", "a different reason entirely", permission_event["blocked_reason"]),
61
+ ),
62
+ ]
63
+ for label, decision in decisions:
64
+ print(f" {label}: should_notify = {decision}")
65
+
66
+ assert decisions[0][1] is True
67
+ assert decisions[1][1] is False
68
+ assert decisions[2][1] is True
69
+ print("\nOK: dedup decisions matched the documented rule (docs/concepts.md).")
70
+
71
+
72
+ if __name__ == "__main__":
73
+ main()