imbi-slackbot 2.8.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.
- imbi_slackbot-2.8.0/.github/workflows/publish.yml +45 -0
- imbi_slackbot-2.8.0/.github/workflows/test.yml +49 -0
- imbi_slackbot-2.8.0/.gitignore +8 -0
- imbi_slackbot-2.8.0/.pre-commit-config.yaml +22 -0
- imbi_slackbot-2.8.0/.python-version +1 -0
- imbi_slackbot-2.8.0/LICENSE +28 -0
- imbi_slackbot-2.8.0/PKG-INFO +77 -0
- imbi_slackbot-2.8.0/README.md +59 -0
- imbi_slackbot-2.8.0/docs/slack-app-manifest.md +57 -0
- imbi_slackbot-2.8.0/justfile +51 -0
- imbi_slackbot-2.8.0/pyproject.toml +189 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/__init__.py +18 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/agent.py +148 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/app.py +91 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/app_status.py +41 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/client.py +73 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/identity.py +155 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/links.py +89 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/mcp.py +248 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/py.typed +0 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/settings.py +111 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/slack_handler.py +223 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/system_prompt.md +76 -0
- imbi_slackbot-2.8.0/src/imbi_slackbot/system_prompt.py +92 -0
- imbi_slackbot-2.8.0/tests/__init__.py +0 -0
- imbi_slackbot-2.8.0/tests/helpers.py +40 -0
- imbi_slackbot-2.8.0/tests/test_agent.py +161 -0
- imbi_slackbot-2.8.0/tests/test_app.py +51 -0
- imbi_slackbot-2.8.0/tests/test_client.py +49 -0
- imbi_slackbot-2.8.0/tests/test_identity.py +135 -0
- imbi_slackbot-2.8.0/tests/test_links.py +100 -0
- imbi_slackbot-2.8.0/tests/test_mcp.py +183 -0
- imbi_slackbot-2.8.0/tests/test_settings.py +64 -0
- imbi_slackbot-2.8.0/tests/test_slack_handler.py +301 -0
- imbi_slackbot-2.8.0/tests/test_system_prompt.py +59 -0
- imbi_slackbot-2.8.0/tests/test_version.py +11 -0
- imbi_slackbot-2.8.0/uv.lock +2494 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
id-token: write
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout repository
|
|
14
|
+
uses: actions/checkout@v6
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v5
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
run: uv python install 3.14
|
|
23
|
+
|
|
24
|
+
- name: Build
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Upload dist
|
|
28
|
+
uses: actions/upload-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: dist
|
|
31
|
+
path: dist/
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment: pypi
|
|
37
|
+
steps:
|
|
38
|
+
- name: Download dist
|
|
39
|
+
uses: actions/download-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
|
|
44
|
+
- name: Publish package
|
|
45
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: "Run Tests"
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [ main ]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [ main ]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
lint:
|
|
13
|
+
name: "Static Analysis"
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout repository
|
|
17
|
+
uses: actions/checkout@v5
|
|
18
|
+
- name: Install just
|
|
19
|
+
uses: extractions/setup-just@v2
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v7
|
|
22
|
+
with:
|
|
23
|
+
enable-cache: true
|
|
24
|
+
- name: Run lint checks
|
|
25
|
+
run: just lint
|
|
26
|
+
|
|
27
|
+
test:
|
|
28
|
+
name: "Automated Tests"
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- name: Checkout repository
|
|
32
|
+
uses: actions/checkout@v5
|
|
33
|
+
- name: Install just
|
|
34
|
+
uses: extractions/setup-just@v2
|
|
35
|
+
- name: Install uv
|
|
36
|
+
uses: astral-sh/setup-uv@v7
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: true
|
|
39
|
+
python-version: "3.14"
|
|
40
|
+
- name: Run tests
|
|
41
|
+
env:
|
|
42
|
+
UV_FROZEN: "1"
|
|
43
|
+
run: just test
|
|
44
|
+
- name: Upload coverage reports
|
|
45
|
+
uses: codecov/codecov-action@v5
|
|
46
|
+
with:
|
|
47
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
48
|
+
files: "build/coverage.xml"
|
|
49
|
+
flags: unittests
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0 # unrelated to the pre-commit utility version
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-executables-have-shebangs
|
|
6
|
+
- id: check-json
|
|
7
|
+
- id: check-shebang-scripts-are-executable
|
|
8
|
+
- id: check-toml
|
|
9
|
+
- id: check-yaml
|
|
10
|
+
- id: end-of-file-fixer
|
|
11
|
+
- id: mixed-line-ending
|
|
12
|
+
- id: trailing-whitespace
|
|
13
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
14
|
+
rev: v0.14.14
|
|
15
|
+
hooks:
|
|
16
|
+
- id: ruff-check
|
|
17
|
+
args: [--fix]
|
|
18
|
+
- id: ruff-format
|
|
19
|
+
- repo: https://github.com/tombi-toml/tombi-pre-commit
|
|
20
|
+
rev: v0.7.27
|
|
21
|
+
hooks:
|
|
22
|
+
- id: tombi-format
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, AWeber
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: imbi-slackbot
|
|
3
|
+
Version: 2.8.0
|
|
4
|
+
Summary: Imbi Slack bot
|
|
5
|
+
Author-email: "Gavin M. Roy" <gavinr@aweber.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.14
|
|
9
|
+
Requires-Dist: anthropic>=0.84.0
|
|
10
|
+
Requires-Dist: fastapi>=0.135.1
|
|
11
|
+
Requires-Dist: fastmcp>=3.0.0
|
|
12
|
+
Requires-Dist: httpx>=0.28.1
|
|
13
|
+
Requires-Dist: imbi-common[databases,mcp,server]==2.8.0
|
|
14
|
+
Requires-Dist: pydantic~=2.12
|
|
15
|
+
Requires-Dist: sentry-sdk>=2.48.0
|
|
16
|
+
Requires-Dist: slack-bolt>=1.21
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# imbi-slackbot
|
|
20
|
+
|
|
21
|
+
A Slack bot for the [Imbi](https://github.com/AWeber-Imbi/imbi) DevOps service
|
|
22
|
+
management platform. It behaves like the
|
|
23
|
+
[Imbi assistant](https://github.com/AWeber-Imbi/imbi-assistant) — giving Claude
|
|
24
|
+
live access to Imbi data and actions through tools — but speaks Slack and acts
|
|
25
|
+
*as the Slack user who asked*, so every answer and action respects that user's
|
|
26
|
+
Imbi permissions.
|
|
27
|
+
|
|
28
|
+
## How It Works
|
|
29
|
+
|
|
30
|
+
The bot connects to Slack over [Socket Mode](https://api.slack.com/apis/socket-mode)
|
|
31
|
+
(an outbound websocket, so no public ingress is required). When a user mentions
|
|
32
|
+
the bot or DMs it, the bot:
|
|
33
|
+
|
|
34
|
+
1. **Resolves the Slack user to an Imbi user.** It looks up the Slack user's
|
|
35
|
+
email (`users.info`), finds the matching active Imbi `User` in the graph, and
|
|
36
|
+
mints a short-lived per-user JWT access token signed with the shared
|
|
37
|
+
`IMBI_AUTH_JWT_SECRET` — exactly the token the Imbi API issues at login. The
|
|
38
|
+
resolution is cached with a TTL. Users with no matching Imbi account get a
|
|
39
|
+
friendly explanation instead.
|
|
40
|
+
2. **Reconstructs the thread context** from Slack (`conversations.replies`) so
|
|
41
|
+
follow-up questions in a thread keep their history. Nothing is persisted
|
|
42
|
+
server-side; the Slack thread *is* the conversation.
|
|
43
|
+
3. **Runs Claude with the Imbi toolset.** Tools are built at startup from the
|
|
44
|
+
Imbi API's `openapi.json` via an in-process
|
|
45
|
+
[FastMCP](https://github.com/jlowin/fastmcp) server, using the shared
|
|
46
|
+
`imbi_common.mcp` exclusion policy (auth/MFA/status/thumbnail and any
|
|
47
|
+
`x-imbi-ai-tool: false` operations are filtered out). The user's minted token
|
|
48
|
+
is forwarded on every tool call, so the API enforces their permissions.
|
|
49
|
+
4. **Replies in-thread** with Claude's answer.
|
|
50
|
+
|
|
51
|
+
A tiny FastAPI app exposes `/status` (port 8004) for health checks; the Socket
|
|
52
|
+
Mode connection runs as a background task alongside it.
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
| Environment variable | Purpose |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `SLACK_BOT_TOKEN` | Slack bot token (`xoxb-…`) |
|
|
59
|
+
| `SLACK_APP_TOKEN` | Slack app-level token for Socket Mode (`xapp-…`) |
|
|
60
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key (enables the bot when set) |
|
|
61
|
+
| `IMBI_AUTH_JWT_SECRET` | Shared HS256 secret used to mint per-user tokens |
|
|
62
|
+
| `IMBI_INTERNAL_API_URL` | In-cluster base URL of the Imbi API |
|
|
63
|
+
| `IMBI_UI_URL` | Public base URL of the Imbi UI, used to build deep links |
|
|
64
|
+
| `IMBI_INTERNAL_UI_URL` | In-cluster UI address for fetching `llms.txt` (e.g. the Caddy frontend); falls back to `IMBI_UI_URL` |
|
|
65
|
+
| `POSTGRES_URL` | Imbi graph (Apache AGE) connection URL |
|
|
66
|
+
| `IMBI_SLACKBOT_MODEL` | Claude model (default `claude-sonnet-4-6`) |
|
|
67
|
+
|
|
68
|
+
See `src/imbi_slackbot/settings.py` for the full set.
|
|
69
|
+
|
|
70
|
+
## Development
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
just setup # install deps + pre-commit hooks
|
|
74
|
+
just test # run the test suite with coverage
|
|
75
|
+
just lint # ruff + mypy + basedpyright
|
|
76
|
+
just serve # run the bot against .env
|
|
77
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# imbi-slackbot
|
|
2
|
+
|
|
3
|
+
A Slack bot for the [Imbi](https://github.com/AWeber-Imbi/imbi) DevOps service
|
|
4
|
+
management platform. It behaves like the
|
|
5
|
+
[Imbi assistant](https://github.com/AWeber-Imbi/imbi-assistant) — giving Claude
|
|
6
|
+
live access to Imbi data and actions through tools — but speaks Slack and acts
|
|
7
|
+
*as the Slack user who asked*, so every answer and action respects that user's
|
|
8
|
+
Imbi permissions.
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
The bot connects to Slack over [Socket Mode](https://api.slack.com/apis/socket-mode)
|
|
13
|
+
(an outbound websocket, so no public ingress is required). When a user mentions
|
|
14
|
+
the bot or DMs it, the bot:
|
|
15
|
+
|
|
16
|
+
1. **Resolves the Slack user to an Imbi user.** It looks up the Slack user's
|
|
17
|
+
email (`users.info`), finds the matching active Imbi `User` in the graph, and
|
|
18
|
+
mints a short-lived per-user JWT access token signed with the shared
|
|
19
|
+
`IMBI_AUTH_JWT_SECRET` — exactly the token the Imbi API issues at login. The
|
|
20
|
+
resolution is cached with a TTL. Users with no matching Imbi account get a
|
|
21
|
+
friendly explanation instead.
|
|
22
|
+
2. **Reconstructs the thread context** from Slack (`conversations.replies`) so
|
|
23
|
+
follow-up questions in a thread keep their history. Nothing is persisted
|
|
24
|
+
server-side; the Slack thread *is* the conversation.
|
|
25
|
+
3. **Runs Claude with the Imbi toolset.** Tools are built at startup from the
|
|
26
|
+
Imbi API's `openapi.json` via an in-process
|
|
27
|
+
[FastMCP](https://github.com/jlowin/fastmcp) server, using the shared
|
|
28
|
+
`imbi_common.mcp` exclusion policy (auth/MFA/status/thumbnail and any
|
|
29
|
+
`x-imbi-ai-tool: false` operations are filtered out). The user's minted token
|
|
30
|
+
is forwarded on every tool call, so the API enforces their permissions.
|
|
31
|
+
4. **Replies in-thread** with Claude's answer.
|
|
32
|
+
|
|
33
|
+
A tiny FastAPI app exposes `/status` (port 8004) for health checks; the Socket
|
|
34
|
+
Mode connection runs as a background task alongside it.
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
| Environment variable | Purpose |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `SLACK_BOT_TOKEN` | Slack bot token (`xoxb-…`) |
|
|
41
|
+
| `SLACK_APP_TOKEN` | Slack app-level token for Socket Mode (`xapp-…`) |
|
|
42
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key (enables the bot when set) |
|
|
43
|
+
| `IMBI_AUTH_JWT_SECRET` | Shared HS256 secret used to mint per-user tokens |
|
|
44
|
+
| `IMBI_INTERNAL_API_URL` | In-cluster base URL of the Imbi API |
|
|
45
|
+
| `IMBI_UI_URL` | Public base URL of the Imbi UI, used to build deep links |
|
|
46
|
+
| `IMBI_INTERNAL_UI_URL` | In-cluster UI address for fetching `llms.txt` (e.g. the Caddy frontend); falls back to `IMBI_UI_URL` |
|
|
47
|
+
| `POSTGRES_URL` | Imbi graph (Apache AGE) connection URL |
|
|
48
|
+
| `IMBI_SLACKBOT_MODEL` | Claude model (default `claude-sonnet-4-6`) |
|
|
49
|
+
|
|
50
|
+
See `src/imbi_slackbot/settings.py` for the full set.
|
|
51
|
+
|
|
52
|
+
## Development
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
just setup # install deps + pre-commit hooks
|
|
56
|
+
just test # run the test suite with coverage
|
|
57
|
+
just lint # ruff + mypy + basedpyright
|
|
58
|
+
just serve # run the bot against .env
|
|
59
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Slack App Setup
|
|
2
|
+
|
|
3
|
+
`imbi-slackbot` connects to Slack over **Socket Mode**, so it needs no public
|
|
4
|
+
URL. Create a Slack app from the manifest below (Slack → *Your Apps* →
|
|
5
|
+
*Create New App* → *From an app manifest*), install it to the workspace, then
|
|
6
|
+
provide the two tokens to the service.
|
|
7
|
+
|
|
8
|
+
## Tokens
|
|
9
|
+
|
|
10
|
+
| Env var | Where to find it |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `SLACK_BOT_TOKEN` | *OAuth & Permissions* → *Bot User OAuth Token* (`xoxb-…`) after install |
|
|
13
|
+
| `SLACK_APP_TOKEN` | *Basic Information* → *App-Level Tokens* → create one with the `connections:write` scope (`xapp-…`) |
|
|
14
|
+
|
|
15
|
+
The bot also needs `ANTHROPIC_API_KEY`, the shared `IMBI_AUTH_JWT_SECRET`, and
|
|
16
|
+
`IMBI_INTERNAL_API_URL` / `POSTGRES_URL` (see the README).
|
|
17
|
+
|
|
18
|
+
## Required scopes & why
|
|
19
|
+
|
|
20
|
+
- `app_mentions:read` — receive `@imbi` mentions in channels.
|
|
21
|
+
- `chat:write` — post replies.
|
|
22
|
+
- `users:read` + `users:read.email` — **the identity bridge**: resolve the
|
|
23
|
+
Slack user to their email, which is matched against the Imbi `User` record.
|
|
24
|
+
- `im:history`, `im:read` — read and respond in direct messages.
|
|
25
|
+
- `channels:history`, `groups:history` — read thread context
|
|
26
|
+
(`conversations.replies`) when mentioned in public/private channels.
|
|
27
|
+
|
|
28
|
+
## Manifest
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
display_information:
|
|
32
|
+
name: Imbi
|
|
33
|
+
description: Query and manage Imbi from Slack
|
|
34
|
+
features:
|
|
35
|
+
bot_user:
|
|
36
|
+
display_name: imbi
|
|
37
|
+
always_online: true
|
|
38
|
+
oauth_config:
|
|
39
|
+
scopes:
|
|
40
|
+
bot:
|
|
41
|
+
- app_mentions:read
|
|
42
|
+
- chat:write
|
|
43
|
+
- users:read
|
|
44
|
+
- users:read.email
|
|
45
|
+
- im:history
|
|
46
|
+
- im:read
|
|
47
|
+
- channels:history
|
|
48
|
+
- groups:history
|
|
49
|
+
settings:
|
|
50
|
+
event_subscriptions:
|
|
51
|
+
bot_events:
|
|
52
|
+
- app_mention
|
|
53
|
+
- message.im
|
|
54
|
+
socket_mode_enabled: true
|
|
55
|
+
org_deploy_enabled: false
|
|
56
|
+
token_rotation_enabled: false
|
|
57
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[doc("Bootstrap the environment and run the bot in the foreground")]
|
|
2
|
+
[group("Testing")]
|
|
3
|
+
serve *ARGS: setup
|
|
4
|
+
uv run --env-file=.env imbi-slackbot serve {{ ARGS }}
|
|
5
|
+
|
|
6
|
+
[default]
|
|
7
|
+
[private]
|
|
8
|
+
ci: lint test
|
|
9
|
+
|
|
10
|
+
[doc("Set up your development environment")]
|
|
11
|
+
[group("Environment")]
|
|
12
|
+
setup:
|
|
13
|
+
uv sync --all-groups --all-extras --frozen
|
|
14
|
+
uv run pre-commit install --install-hooks --overwrite
|
|
15
|
+
|
|
16
|
+
[doc("Run tests")]
|
|
17
|
+
[group("Testing")]
|
|
18
|
+
test: setup
|
|
19
|
+
uv run pytest
|
|
20
|
+
|
|
21
|
+
[doc("Run linters")]
|
|
22
|
+
[group("Testing")]
|
|
23
|
+
lint: setup
|
|
24
|
+
uv run pre-commit run --all-files
|
|
25
|
+
uv run basedpyright
|
|
26
|
+
uv run mypy
|
|
27
|
+
|
|
28
|
+
[doc("Reformat code (optionally pass specific files)")]
|
|
29
|
+
[group("Development")]
|
|
30
|
+
format *FILES: setup
|
|
31
|
+
#!/usr/bin/env sh
|
|
32
|
+
set -ex
|
|
33
|
+
if [ "{{FILES}}" = '' ]; then
|
|
34
|
+
args='--all-files'
|
|
35
|
+
else
|
|
36
|
+
args='--files {{FILES}}'
|
|
37
|
+
fi
|
|
38
|
+
uv run pre-commit run ruff-format $args
|
|
39
|
+
uv run pre-commit run tombi-format $args
|
|
40
|
+
|
|
41
|
+
[doc("Remove runtime artifacts")]
|
|
42
|
+
[group("Environment")]
|
|
43
|
+
clean:
|
|
44
|
+
rm -f .coverage
|
|
45
|
+
rm -fR build
|
|
46
|
+
|
|
47
|
+
[confirm]
|
|
48
|
+
[doc("Remove caches, virtual env, and output files")]
|
|
49
|
+
[group("Environment")]
|
|
50
|
+
real-clean: clean
|
|
51
|
+
rm -fR .venv .*_cache dist
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "imbi-slackbot"
|
|
3
|
+
version = "2.8.0"
|
|
4
|
+
description = "Imbi Slack bot"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
license = "BSD-3-Clause"
|
|
8
|
+
authors = [{ name = "Gavin M. Roy", email = "gavinr@aweber.com" }]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"anthropic>=0.84.0",
|
|
11
|
+
"fastapi>=0.135.1",
|
|
12
|
+
"fastmcp>=3.0.0",
|
|
13
|
+
"httpx>=0.28.1",
|
|
14
|
+
"imbi-common[databases,mcp,server]==2.8.0",
|
|
15
|
+
"pydantic~=2.12",
|
|
16
|
+
"sentry-sdk>=2.48.0",
|
|
17
|
+
"slack-bolt>=1.21",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
imbi-slackbot = "imbi_slackbot.app:cli"
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"basedpyright>=1.37.1",
|
|
26
|
+
"coverage[toml]>=7.13.1",
|
|
27
|
+
"mypy~=1.19.1",
|
|
28
|
+
"pre-commit>=4.5.1",
|
|
29
|
+
"pytest>=9.0.2",
|
|
30
|
+
"pytest-cov>=7.0.0",
|
|
31
|
+
"python-dotenv>=1.1.0",
|
|
32
|
+
"ruff~=0.14.6",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["hatchling"]
|
|
37
|
+
build-backend = "hatchling.build"
|
|
38
|
+
|
|
39
|
+
[tool.coverage.report]
|
|
40
|
+
fail_under = 90
|
|
41
|
+
show_missing = true
|
|
42
|
+
|
|
43
|
+
[tool.coverage.run]
|
|
44
|
+
branch = true
|
|
45
|
+
source = ["src"]
|
|
46
|
+
|
|
47
|
+
[tool.mypy]
|
|
48
|
+
namespace_packages = true
|
|
49
|
+
show_error_codes = true
|
|
50
|
+
strict = true
|
|
51
|
+
packages = ["imbi_slackbot", "tests"]
|
|
52
|
+
|
|
53
|
+
[[tool.mypy.overrides]]
|
|
54
|
+
module = "tests.*"
|
|
55
|
+
disallow_untyped_defs = false
|
|
56
|
+
disallow_incomplete_defs = false
|
|
57
|
+
disallow_untyped_calls = false
|
|
58
|
+
disallow_any_expr = false
|
|
59
|
+
implicit_reexport = true
|
|
60
|
+
warn_return_any = false
|
|
61
|
+
disable_error_code = [
|
|
62
|
+
"attr-defined",
|
|
63
|
+
"call-arg",
|
|
64
|
+
"arg-type",
|
|
65
|
+
"type-arg",
|
|
66
|
+
"union-attr",
|
|
67
|
+
"index",
|
|
68
|
+
"var-annotated"
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[tool.pyright]
|
|
72
|
+
deprecateTypingAliases = true
|
|
73
|
+
reportMissingSuperCall = "hint"
|
|
74
|
+
typeCheckingMode = "strict"
|
|
75
|
+
|
|
76
|
+
[[tool.pyright.executionEnvironments]]
|
|
77
|
+
root = "src"
|
|
78
|
+
reportUnusedFunction = "warning"
|
|
79
|
+
|
|
80
|
+
[[tool.pyright.executionEnvironments]]
|
|
81
|
+
root = "tests"
|
|
82
|
+
reportUnusedFunction = "none"
|
|
83
|
+
reportUnusedVariable = "none"
|
|
84
|
+
reportPrivateUsage = "none"
|
|
85
|
+
reportAttributeAccessIssue = "none"
|
|
86
|
+
reportMissingParameterType = "none"
|
|
87
|
+
reportUnknownLambdaType = "none"
|
|
88
|
+
reportUnknownParameterType = "none"
|
|
89
|
+
reportUnknownVariableType = "none"
|
|
90
|
+
reportMissingTypeArgument = "none"
|
|
91
|
+
reportCallIssue = "none"
|
|
92
|
+
reportOptionalMemberAccess = "none"
|
|
93
|
+
reportOptionalSubscript = "none"
|
|
94
|
+
reportUnknownMemberType = "none"
|
|
95
|
+
reportArgumentType = "none"
|
|
96
|
+
reportUnknownArgumentType = "none"
|
|
97
|
+
extraPaths = ["src", "."]
|
|
98
|
+
|
|
99
|
+
[tool.pytest.ini_options]
|
|
100
|
+
addopts = [
|
|
101
|
+
"--cov=src/imbi_slackbot",
|
|
102
|
+
"--cov-report=xml:build/coverage.xml",
|
|
103
|
+
"--cov-report=term",
|
|
104
|
+
]
|
|
105
|
+
testpaths = ["tests"]
|
|
106
|
+
|
|
107
|
+
[tool.ruff]
|
|
108
|
+
line-length = 79
|
|
109
|
+
target-version = "py314"
|
|
110
|
+
|
|
111
|
+
[tool.ruff.format]
|
|
112
|
+
quote-style = "single"
|
|
113
|
+
|
|
114
|
+
[tool.ruff.lint]
|
|
115
|
+
select = [
|
|
116
|
+
"A",
|
|
117
|
+
"ANN",
|
|
118
|
+
"ARG",
|
|
119
|
+
"ASYNC",
|
|
120
|
+
"B",
|
|
121
|
+
"BLE",
|
|
122
|
+
"C4",
|
|
123
|
+
"C90",
|
|
124
|
+
"DTZ",
|
|
125
|
+
"E",
|
|
126
|
+
"ERA",
|
|
127
|
+
"F",
|
|
128
|
+
"FLY",
|
|
129
|
+
"FURB",
|
|
130
|
+
"G",
|
|
131
|
+
"I",
|
|
132
|
+
"LOG",
|
|
133
|
+
"N",
|
|
134
|
+
"PIE",
|
|
135
|
+
"PL",
|
|
136
|
+
"PTH",
|
|
137
|
+
"Q",
|
|
138
|
+
"RET",
|
|
139
|
+
"RSE",
|
|
140
|
+
"RUF",
|
|
141
|
+
"S",
|
|
142
|
+
"SIM",
|
|
143
|
+
"T10",
|
|
144
|
+
"T20",
|
|
145
|
+
"TC",
|
|
146
|
+
"TRY",
|
|
147
|
+
"W",
|
|
148
|
+
]
|
|
149
|
+
ignore = [
|
|
150
|
+
"ANN401",
|
|
151
|
+
"ARG002",
|
|
152
|
+
"PLR0913",
|
|
153
|
+
"PLR2004",
|
|
154
|
+
"TRY003",
|
|
155
|
+
"TRY400",
|
|
156
|
+
]
|
|
157
|
+
flake8-quotes = { inline-quotes = "single" }
|
|
158
|
+
|
|
159
|
+
[tool.ruff.lint.flake8-type-checking]
|
|
160
|
+
runtime-evaluated-base-classes = ["pydantic.BaseModel"]
|
|
161
|
+
|
|
162
|
+
[tool.ruff.lint.mccabe]
|
|
163
|
+
max-complexity = 15
|
|
164
|
+
|
|
165
|
+
[tool.ruff.lint.per-file-ignores]
|
|
166
|
+
"src/imbi_slackbot/client.py" = ["PLW0603"]
|
|
167
|
+
"src/imbi_slackbot/identity.py" = ["PLW0603"]
|
|
168
|
+
"src/imbi_slackbot/links.py" = ["PLW0603"]
|
|
169
|
+
"src/imbi_slackbot/mcp.py" = ["PLW0603"]
|
|
170
|
+
"src/imbi_slackbot/settings.py" = ["PLW0603"]
|
|
171
|
+
"src/imbi_slackbot/slack_handler.py" = ["PLW0603"]
|
|
172
|
+
"src/imbi_slackbot/system_prompt.py" = ["PLW0603"]
|
|
173
|
+
"tests/**/*.py" = [
|
|
174
|
+
"ANN",
|
|
175
|
+
"ARG005",
|
|
176
|
+
"B018",
|
|
177
|
+
"N802",
|
|
178
|
+
"PLC0415",
|
|
179
|
+
"RUF059",
|
|
180
|
+
"S",
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
[tool.uv]
|
|
184
|
+
exclude-newer = "7 days"
|
|
185
|
+
exclude-newer-package = { "imbi-common" = false }
|
|
186
|
+
|
|
187
|
+
[[tool.uv.index]]
|
|
188
|
+
url = "https://pypi.org/simple"
|
|
189
|
+
default = true
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import re as _re
|
|
2
|
+
from importlib import metadata as _metadata
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
version = _metadata.version('imbi-slackbot')
|
|
6
|
+
except _metadata.PackageNotFoundError:
|
|
7
|
+
version = '0.0.0'
|
|
8
|
+
|
|
9
|
+
version_info: list[int | str] = []
|
|
10
|
+
for _part in version.split('.'):
|
|
11
|
+
_match = _re.fullmatch(r'(\d+)(.*)', _part)
|
|
12
|
+
if _match is None:
|
|
13
|
+
version_info.append(_part)
|
|
14
|
+
else:
|
|
15
|
+
version_info.append(int(_match.group(1)))
|
|
16
|
+
if _match.group(2):
|
|
17
|
+
version_info.append(_match.group(2))
|
|
18
|
+
del _metadata, _re
|