slack-claude-code 0.1.5__py3-none-any.whl

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 (65) hide show
  1. slack_claude_code-0.1.5.dist-info/METADATA +226 -0
  2. slack_claude_code-0.1.5.dist-info/RECORD +65 -0
  3. slack_claude_code-0.1.5.dist-info/WHEEL +4 -0
  4. slack_claude_code-0.1.5.dist-info/entry_points.txt +4 -0
  5. slack_claude_code-0.1.5.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +0 -0
  7. src/agents/__init__.py +10 -0
  8. src/agents/orchestrator.py +315 -0
  9. src/agents/roles.py +164 -0
  10. src/app.py +726 -0
  11. src/approval/__init__.py +1 -0
  12. src/approval/handler.py +302 -0
  13. src/approval/plan_manager.py +226 -0
  14. src/approval/slack_ui.py +312 -0
  15. src/claude/__init__.py +2 -0
  16. src/claude/streaming.py +348 -0
  17. src/claude/subprocess_executor.py +498 -0
  18. src/cli.py +177 -0
  19. src/config.py +231 -0
  20. src/config_storage.py +138 -0
  21. src/database/__init__.py +1 -0
  22. src/database/migrations.py +185 -0
  23. src/database/models.py +262 -0
  24. src/database/repository.py +762 -0
  25. src/git/__init__.py +1 -0
  26. src/git/models.py +62 -0
  27. src/git/service.py +352 -0
  28. src/handlers/__init__.py +53 -0
  29. src/handlers/actions.py +1030 -0
  30. src/handlers/agents.py +270 -0
  31. src/handlers/base.py +163 -0
  32. src/handlers/basic.py +137 -0
  33. src/handlers/claude_cli.py +371 -0
  34. src/handlers/git.py +317 -0
  35. src/handlers/mode.py +121 -0
  36. src/handlers/notifications.py +192 -0
  37. src/handlers/parallel.py +71 -0
  38. src/handlers/queue.py +276 -0
  39. src/handlers/session_management.py +49 -0
  40. src/hooks/__init__.py +4 -0
  41. src/hooks/registry.py +200 -0
  42. src/hooks/types.py +61 -0
  43. src/question/__init__.py +1 -0
  44. src/question/manager.py +434 -0
  45. src/question/slack_ui.py +328 -0
  46. src/tasks/__init__.py +1 -0
  47. src/tasks/manager.py +350 -0
  48. src/utils/__init__.py +1 -0
  49. src/utils/detail_cache.py +89 -0
  50. src/utils/file_downloader.py +138 -0
  51. src/utils/formatters/__init__.py +1 -0
  52. src/utils/formatters/base.py +180 -0
  53. src/utils/formatters/command.py +148 -0
  54. src/utils/formatters/directory.py +64 -0
  55. src/utils/formatters/job.py +184 -0
  56. src/utils/formatters/markdown.py +85 -0
  57. src/utils/formatters/plan.py +183 -0
  58. src/utils/formatters/queue.py +103 -0
  59. src/utils/formatters/session.py +137 -0
  60. src/utils/formatters/streaming.py +85 -0
  61. src/utils/formatters/tool_blocks.py +343 -0
  62. src/utils/formatting.py +148 -0
  63. src/utils/slack_helpers.py +330 -0
  64. src/utils/streaming.py +258 -0
  65. src/utils/validators.py +36 -0
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: slack-claude-code
3
+ Version: 0.1.5
4
+ Summary: Slack app for running Claude Code CLI commands
5
+ License-File: LICENSE
6
+ Author: Dan
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
15
+ Requires-Dist: aiohttp (>=3.13.2,<4.0.0)
16
+ Requires-Dist: aiosqlite (>=0.21.0,<0.22.0)
17
+ Requires-Dist: cryptography (>=44.0.0,<45.0.0)
18
+ Requires-Dist: loguru (>=0.7.0,<0.8.0)
19
+ Requires-Dist: pydantic-settings (>=2.7.0,<3.0.0)
20
+ Requires-Dist: slack-bolt (>=1.27.0,<2.0.0)
21
+ Description-Content-Type: text/markdown
22
+
23
+ <p align="center">
24
+ <img src="assets/repo_logo.png" alt="Slack Claude Code Bot" width="1000">
25
+ </p>
26
+
27
+ <p align="center">
28
+ <a href="https://pypi.org/project/slack-claude-code/"><img src="https://img.shields.io/pypi/v/slack-claude-code" alt="PyPI version"></a>
29
+ <a href="https://pypi.org/project/slack-claude-code/"><img src="https://img.shields.io/pypi/pyversions/slack-claude-code" alt="Python versions"></a>
30
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
31
+ <a href="https://github.com/djkelleher/slack-claude-code/actions/workflows/tests.yml"><img src="https://github.com/djkelleher/slack-claude-code/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
32
+ </p>
33
+
34
+ **Claude Code, but in Slack.** Access Claude Code remotely from any device, or use it full-time for a better UI experience.
35
+
36
+ ## Why Slack?
37
+
38
+ | Feature | Terminal | Slack |
39
+ |---------|----------|-------|
40
+ | **Code blocks** | Plain text | Syntax-highlighted with copy button |
41
+ | **Long output** | Scrolls off screen | "View Details" modal |
42
+ | **Permissions** | Y/n prompts | Approve/Deny buttons |
43
+ | **Parallel work** | Multiple terminals | Threads = isolated sessions |
44
+ | **File sharing** | `cat` or copy-paste | Drag & drop with preview |
45
+ | **Notifications** | Watch the terminal | Alerts when tasks complete |
46
+ | **Streaming** | Live terminal output | Watch responses as they generate |
47
+ | **Smart context** | Manual file inclusion | Frequently-used files auto-included |
48
+
49
+ ## Installation
50
+
51
+ ### Prerequisites
52
+ - Python 3.10+
53
+ - [Claude Code CLI](https://github.com/anthropics/claude-code) installed and authenticated
54
+
55
+ ### 1. Install the `ccslack` executable
56
+ ```bash
57
+ pipx install slack-claude-code
58
+ ```
59
+
60
+ ### 2. Create Slack App
61
+ Go to https://api.slack.com/apps → "Create New App" → "From scratch"
62
+
63
+ **Socket Mode**: Enable and create an app-level token with `connections:write` scope (save the `xapp-` token)
64
+
65
+ **Bot Token Scopes** (OAuth & Permissions):
66
+ - `chat:write`, `commands`, `channels:history`, `app_mentions:read`, `files:read`, `files:write`
67
+
68
+ **Event Subscriptions**: Enable and add `message.channels`, `app_mention`
69
+
70
+ **App Icon**: In "Basic Information" → "Display Information", upload `assets/claude_logo.png` from this repo as the app icon
71
+
72
+ **Slash Commands**: Add the commands from the tables below (or the subset that you plan to use)
73
+
74
+ #### Configuration
75
+ Customize Claude's behavior for your workflow.
76
+
77
+ | Command | Description | Example |
78
+ |---------|-------------|---------|
79
+ | `/model` | Show or change AI model | `/model sonnet` |
80
+ | `/mode` | View or set permission mode | `/mode`, `/mode plan` |
81
+ | `/permissions` | View/update permissions | `/permissions` |
82
+ | `/notifications` | Configure notifications | `/notifications` |
83
+
84
+ #### Session Management
85
+ Each Slack thread maintains an isolated Claude session with its own context.
86
+
87
+ | Command | Description | Example |
88
+ |---------|-------------|---------|
89
+ | `/clear` | Reset conversation | `/clear` |
90
+ | `/compact` | Compact context | `/compact` |
91
+ | `/cost` | Show session cost | `/cost` |
92
+ | `/resume` | Resume previous session | `/resume` |
93
+ | `/sessions` | List active sessions | `/sessions` |
94
+ | `/session-cleanup` | Clean up inactive sessions | `/session-cleanup` |
95
+
96
+ #### Navigation
97
+ Control the working directory for Claude's file operations.
98
+
99
+ | Command | Description | Example |
100
+ |---------|-------------|---------|
101
+ | `/ls` | List directory contents | `/ls`, `/ls src/` |
102
+ | `/cd` | Change working directory | `/cd /home/user/project`, `cd subfolder`, `cd ..` |
103
+ | `/pwd` | Print working directory | `/pwd` |
104
+ | `/add-dir` | Add directory to context | `/add-dir ./lib` |
105
+
106
+ #### CLI Tools
107
+ Direct access to Claude Code CLI functionality.
108
+
109
+ | Command | Description | Example |
110
+ |---------|-------------|---------|
111
+ | `/init` | Initialize project config | `/init` |
112
+ | `/memory` | View/edit Claude's memory | `/memory` |
113
+ | `/review` | Review code changes | `/review` |
114
+ | `/doctor` | Diagnose installation | `/doctor` |
115
+ | `/stats` | Show session statistics | `/stats` |
116
+ | `/context` | Display context info | `/context` |
117
+ | `/todos` | List and manage todos | `/todos` |
118
+ | `/claude-help` | Show Claude Code help | `/claude-help` |
119
+ | `/claude-config` | Show configuration | `/claude-config` |
120
+
121
+ #### Multi-Agent Tasks
122
+ Autonomous Planner → Worker → Evaluator pipeline for complex tasks. Iterates up to 3 times until complete.
123
+
124
+ | Command | Description | Example |
125
+ |---------|-------------|---------|
126
+ | `/task` | Start a multi-agent task | `/task add unit tests for UserService` |
127
+ | `/tasks` | List active tasks with status | `/tasks` |
128
+ | `/task-cancel` | Cancel a running task | `/task-cancel abc123` |
129
+
130
+ #### Command Queue
131
+ Queue commands for sequential execution while preserving Claude's session context across items.
132
+
133
+ | Command | Description | Example |
134
+ |---------|-------------|---------|
135
+ | `/q` | Add command to queue | `/q analyze the API endpoints` |
136
+ | `/qv` | View queue status | `/qv` |
137
+ | `/qc` | Clear pending queue | `/qc` |
138
+ | `/qr` | Remove specific item | `/qr 5` |
139
+
140
+ #### Jobs & Control
141
+ Monitor and control long-running operations with real-time progress updates.
142
+
143
+ | Command | Description | Example |
144
+ |---------|-------------|---------|
145
+ | `/st` | Show active job status | `/st` |
146
+ | `/cc` | Cancel jobs | `/cc` or `/cc abc123` |
147
+ | `/esc` | Send interrupt (Ctrl+C) | `/esc` |
148
+
149
+ #### Git
150
+ Full git workflow without leaving Slack. Includes branch name and commit message validation.
151
+
152
+ | Command | Description | Example |
153
+ |---------|-------------|---------|
154
+ | `/status` | Show branch and changes | `/status` |
155
+ | `/diff` | Show uncommitted changes | `/diff --staged` |
156
+ | `/commit` | Commit staged changes | `/commit fix: resolve race condition` |
157
+ | `/branch` | Manage branches | `/branch create feature/auth` |
158
+
159
+
160
+ ### 3. Configure
161
+
162
+ Use the built-in config CLI to securely store your Slack credentials:
163
+
164
+ ```bash
165
+ ccslack-config set SLACK_BOT_TOKEN=xoxb-...
166
+ ccslack-config set SLACK_APP_TOKEN=xapp-...
167
+ ccslack-config set SLACK_SIGNING_SECRET=...
168
+ ```
169
+
170
+ **Config CLI Commands:**
171
+ | Command | Description |
172
+ |---------|-------------|
173
+ | `ccslack-config set KEY=VALUE` | Store a configuration value |
174
+ | `ccslack-config get KEY` | Retrieve a configuration value |
175
+ | `ccslack-config list` | List all stored configuration |
176
+ | `ccslack-config delete KEY` | Remove a configuration value |
177
+ | `ccslack-config path` | Show config file locations |
178
+
179
+ Configuration is encrypted and stored in `~/.slack-claude-code/config.enc`. Sensitive values (tokens, secrets) are masked when displayed.
180
+
181
+ **Alternative:** You can also use environment variables or a `.env` file. Config values take precedence over environment variables.
182
+
183
+ **Where to find these values:**
184
+ - `SLACK_BOT_TOKEN`: Your App → OAuth & Permissions → Bot User OAuth Token
185
+ - `SLACK_APP_TOKEN`: Your App → Basic Information → App-Level Tokens → (token you created with `connections:write`)
186
+ - `SLACK_SIGNING_SECRET`: Your App → Basic Information → App Credentials → Signing Secret
187
+
188
+ ### 4. Start the Slack bot
189
+ You can now run `ccslack` in your terminal. The working directory where you start the executable will be the default working directory for your Claude Code session(s). If you have a .env file in this directory, it will automatically be loaded.
190
+
191
+ ## Usage
192
+
193
+ Type messages in any channel where the bot is installed. The main channel is a single Claude Code session. If you click `reply` to any message and start a thread, this will be a new Claude Code session.
194
+
195
+ ## Architecture
196
+
197
+ ```
198
+ src/
199
+ ├── app.py # Main entry point
200
+ ├── config.py # Configuration
201
+ ├── database/ # SQLite persistence (models, migrations, repository)
202
+ ├── claude/ # Claude CLI integration (streaming)
203
+ ├── handlers/ # Slack command handlers
204
+ ├── agents/ # Multi-agent orchestration (planner→worker→evaluator)
205
+ ├── approval/ # Permission & plan approval handling
206
+ ├── git/ # Git operations (status, diff, commit, branch)
207
+ ├── hooks/ # Event hook system
208
+ ├── question/ # AskUserQuestion tool support
209
+ ├── tasks/ # Background task management
210
+ └── utils/ # Formatters, helpers, validators
211
+ ```
212
+
213
+ ## Troubleshooting
214
+
215
+ | Problem | Solution |
216
+ |---------|----------|
217
+ | Configuration errors on startup | Check `.env` has all required tokens |
218
+ | Commands not appearing | Verify slash commands in Slack app settings |
219
+
220
+ ## License
221
+
222
+ MIT
223
+
224
+ - - -
225
+
226
+ Congratulations, you can now use Claude Code from anywhere 🎉💪
@@ -0,0 +1,65 @@
1
+ src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ src/agents/__init__.py,sha256=XajhwXQX2DmxDoovXa7tkf0TnQ7tgEfNf5PDA3WuOaU,210
3
+ src/agents/orchestrator.py,sha256=8Xi_K_NuOWt8q4fh_OoRm3suntqAySYysj4oXSL8dyQ,10187
4
+ src/agents/roles.py,sha256=nTsO1wTjrkpiLYcXweX0KJr_s1kIeB9NRTYzfX05t6w,4464
5
+ src/app.py,sha256=f69KgfMb7qq9MiER2vbMVV9xvKAOJ1fQS4RrzS-66LE,32038
6
+ src/approval/__init__.py,sha256=39OldyhgimZqGuB_RMrc9cRafpqeU497w-bG0dWJMRI,46
7
+ src/approval/handler.py,sha256=KEN50hB4IkE6urhJ2JaYPweAnvtceUx7JlzFvvnyPkQ,9444
8
+ src/approval/plan_manager.py,sha256=PVhyqWiphvr72aKlb8tHFDZBcEx8Svka2ICk7ceBqAg,6754
9
+ src/approval/slack_ui.py,sha256=rcrSTCytJIdWoT8unPTKuufgJbcy52HqkMmjobXxr4g,7671
10
+ src/claude/__init__.py,sha256=lVuqdU_IGGwuuPrxpB5hZU32MPrqLzd5wxqdu_uJHbY,103
11
+ src/claude/streaming.py,sha256=FvlEt1zWKG_Rti4jl5-wdkCByaEZmvw899fipxAdqGA,14166
12
+ src/claude/subprocess_executor.py,sha256=kG7d7oy_7f1qjJp9-TeJ6fZhHANnf3zOmJgmmVR9YI8,21730
13
+ src/cli.py,sha256=SYaTHNk3p9KTdqxeLgYZTW_N--yLbtNpjxZFbsHgvoQ,4642
14
+ src/config.py,sha256=dpJMdn3e-MBcxb8mERkQk4JUym5loX6LkHh5n8OOx0Y,7305
15
+ src/config_storage.py,sha256=zttA1u-lmKNjy424v2j75s-TCZOBiwSrBh6XG5i8U_s,4346
16
+ src/database/__init__.py,sha256=MAJqxqwvti69iZCcBRHiuN42tUvDI10rXO2r-ms5gp4,45
17
+ src/database/migrations.py,sha256=JZCKiNXXGkalCLVnD4Ml_gsASe005jCdD0592XCvtsI,6773
18
+ src/database/models.py,sha256=TltB9Yhsdc7w-W9fLb9vzu-wUnUhb7pEw2t5e5LNoY4,9150
19
+ src/database/repository.py,sha256=nNt3j1sy-HupszaLQ2Hez3FP33qaQMljildBJ_OQqHM,30288
20
+ src/git/__init__.py,sha256=cV5jpkmZRlpj4eBLHvwliA3Bc1dyEb65K8la9VqEjew,54
21
+ src/git/models.py,sha256=hRdHffabRuZuRkgyTJOJe8ridqC5mErSvmlO91VWDTY,1718
22
+ src/git/service.py,sha256=JS75Q2R-D_WTnLNgLahDYXX4lyrfhi24DOAc-5KnPoc,12394
23
+ src/handlers/__init__.py,sha256=c3f34SBl1jDU2n1W0al-WcqSnrCTeESOTqCOaKIAhnY,1646
24
+ src/handlers/actions.py,sha256=A3xareMrWw-FPLblHlbQ-_PjbVfxAiY7zCLqklM8UNM,37022
25
+ src/handlers/agents.py,sha256=AFpj_Sa4IXwU3oMkwNts-w504UxZ0niQgXnnkHEFBIk,10217
26
+ src/handlers/base.py,sha256=HFYxdL-vJUbgTf_C5FwlgzLIHTOGVOGwh_bAerdfVXU,5045
27
+ src/handlers/basic.py,sha256=mm94kELgdcwOwtk3ehlP2ETUpmQKnzWbfLjoEBEkkV0,5393
28
+ src/handlers/claude_cli.py,sha256=T3AK42hfr5fl9ydmr288P71wXp0y93ooL1REqUSu0II,14631
29
+ src/handlers/git.py,sha256=KZDkUrd3aksz6huQdcsO8r598yg7CnOmh0ABYX1GZwo,12490
30
+ src/handlers/mode.py,sha256=lMDu2s5WSjJftv0nd46vRM2ent4cgF2C31mTTZ48LIE,4167
31
+ src/handlers/notifications.py,sha256=dXHI8Sy4wPq2nHuhXBt7uiPyJZKf3jnmF_F0WJ2dAlM,7523
32
+ src/handlers/parallel.py,sha256=EnYUdoZBcoA2ZqfE2ng2i7xyJmYJS45ds3EQCn2cGnk,2606
33
+ src/handlers/queue.py,sha256=g8gTp88J1Idc6BMFgyDV-TGq_4IC1drKOuUQrTYa9Fs,9578
34
+ src/handlers/session_management.py,sha256=bl4_98G6_ttWJlCQ68g8COJosBbuJ4LERyGMsY9yBzI,1730
35
+ src/hooks/__init__.py,sha256=RH90banbNzw0ew-bkKdKV5ztlOotObMmz-t21Hj6zl4,165
36
+ src/hooks/registry.py,sha256=WDzY44bOLSJYQ-CWFIoGujps7TR4xWPTLTL8G4dUIcU,6205
37
+ src/hooks/types.py,sha256=Ii5tf-aQnCyBoz2B0fLBlrt_KojfALVeSOrtS-ASJ2U,1404
38
+ src/question/__init__.py,sha256=qRO6m9GTxepXekPF6WoNtDDqyQmG5U5FCyqs9zI4XNo,59
39
+ src/question/manager.py,sha256=sauP_PIk8lnKyTcvHC2hQQwFgBB_qrNiFzgZlLFREjE,13485
40
+ src/question/slack_ui.py,sha256=cFupRThaGCUEtYkTqISJAaWpFVfnh9eRYWZPg0le-O8,9088
41
+ src/tasks/__init__.py,sha256=mNU8fdvI4Ag3iQhewSLIF9oo_-m1Wtcimfw18M8d6fY,33
42
+ src/tasks/manager.py,sha256=1j1uoEfazVEhER3oRByfAtHmq4efkpavcnO3CmpCypw,10206
43
+ src/utils/__init__.py,sha256=B-ohn1ey6nbzxmoe7OrAmxeWC5TdVoU00YJIb1YSbjg,50
44
+ src/utils/detail_cache.py,sha256=sq1nIsgQUgHuF5AW82lu8p-tZwlHCZZiMJQR5kRI37k,2233
45
+ src/utils/file_downloader.py,sha256=u2lOps_JCJV3K381J7DiUS-0vj6YJGuALiUJMthoIqE,4923
46
+ src/utils/formatters/__init__.py,sha256=8dnaLxUxTs1Sv138r5BUavSA-tj580vO5T7rc-9g0qA,64
47
+ src/utils/formatters/base.py,sha256=N5X0wPOOhO_BBHCP8-TMIEKv-iLoOheRSdmLve0vlXM,5833
48
+ src/utils/formatters/command.py,sha256=YaKIvbblLvL6otySJwAskRt-8khha5NE2ov4ZobsPjY,4081
49
+ src/utils/formatters/directory.py,sha256=anYs8uS6Md5udHAzm7pV2CrgkJOVt_n1N3IekgZBCqI,1672
50
+ src/utils/formatters/job.py,sha256=vfrEu-JdEvLLMrQxNPbfkqb-etld2_3OdeewEatsOdc,5838
51
+ src/utils/formatters/markdown.py,sha256=xQqs7RBtdufWkf7YwpeNA4Ph13RBcD1irsy7Bu8c5vQ,2870
52
+ src/utils/formatters/plan.py,sha256=ijVkghHrxAKKtf04op2N1dN2ayvndf6xEDPGBM9gWns,4723
53
+ src/utils/formatters/queue.py,sha256=ObRLx5oWEUfpJw2lUZru2XY05lLTdP5M2sqpx-2Gx0w,2987
54
+ src/utils/formatters/session.py,sha256=o37ZX-_W2NVCT1VNUi01YOhgJPB0QgPyz9rsyY3W9-I,4158
55
+ src/utils/formatters/streaming.py,sha256=HwX-xIsgXTr8tpWCHQA2ZsErFoutN-E_S2tNME5nZQQ,2508
56
+ src/utils/formatters/tool_blocks.py,sha256=QY7rdo7dek9uFMDYDyZgHvCo0QOaYJL7J3siPMcl2f0,9582
57
+ src/utils/formatting.py,sha256=IKT-r8JaFnrQwWA4zW8UWMBj1Za0BVTha2E7L3uexb8,4445
58
+ src/utils/slack_helpers.py,sha256=U9P-YJ67RYROOBGsC9FdrdCCR4rojAAJ5RKrJlCL82k,9466
59
+ src/utils/streaming.py,sha256=ypP_jyD-9hOXfkMZFspVXkacDq0P3JKAMuBqxiuZzs4,9359
60
+ src/utils/validators.py,sha256=dOwr5WITYpANipH5uIOl6F7dBueUS3vPKqRNFcxhE-I,1075
61
+ slack_claude_code-0.1.5.dist-info/METADATA,sha256=5F5j4LyBkveOGI2_0J6jedImq_aWRIiyeJhpXZnvcsU,9487
62
+ slack_claude_code-0.1.5.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
63
+ slack_claude_code-0.1.5.dist-info/entry_points.txt,sha256=feO-FYg1CvU17Cu4PrUiZfgm3xzlRy3-j4RWAI8hReY,66
64
+ slack_claude_code-0.1.5.dist-info/licenses/LICENSE,sha256=z_UgzTmrZGQju-Lm9z-Ge8BZHioEvlEUh-doyIVN5mE,1060
65
+ slack_claude_code-0.1.5.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.3.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ ccslack=src.app:run
3
+ ccslack-config=src.cli:run
4
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dan
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.
src/__init__.py ADDED
File without changes
src/agents/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Multi-agent workflow orchestration."""
2
+
3
+ from .orchestrator import (
4
+ AgentTask,
5
+ EvalResult,
6
+ MultiAgentOrchestrator,
7
+ TaskStatus,
8
+ WorkflowResult,
9
+ )
10
+ from .roles import AgentConfig, AgentRole
@@ -0,0 +1,315 @@
1
+ """Multi-agent workflow orchestrator.
2
+
3
+ Coordinates Planner -> Worker -> Evaluator pipeline for complex tasks.
4
+ """
5
+
6
+ import asyncio
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from enum import Enum
10
+ from typing import Awaitable, Callable, Optional
11
+
12
+ from loguru import logger
13
+
14
+ from ..claude.subprocess_executor import ExecutionResult, SubprocessExecutor
15
+ from .roles import AgentRole, format_task_prompt
16
+
17
+
18
+ class TaskStatus(Enum):
19
+ """Status of an agent task."""
20
+
21
+ PENDING = "pending"
22
+ PLANNING = "planning"
23
+ WORKING = "working"
24
+ EVALUATING = "evaluating"
25
+ COMPLETED = "completed"
26
+ FAILED = "failed"
27
+ CANCELLED = "cancelled"
28
+
29
+
30
+ class EvalResult(Enum):
31
+ """Evaluation result from evaluator agent."""
32
+
33
+ COMPLETE = "complete"
34
+ PARTIAL = "partial"
35
+ INCOMPLETE = "incomplete"
36
+ FAILED = "failed"
37
+
38
+
39
+ @dataclass
40
+ class AgentTask:
41
+ """A task being processed by the multi-agent workflow."""
42
+
43
+ task_id: str
44
+ description: str
45
+ channel_id: str
46
+ working_directory: str = "~"
47
+ status: TaskStatus = TaskStatus.PENDING
48
+ plan_output: Optional[str] = None
49
+ work_output: Optional[str] = None
50
+ eval_output: Optional[str] = None
51
+ eval_result: Optional[EvalResult] = None
52
+ error_message: Optional[str] = None
53
+ turn_count: int = 0
54
+ max_turns: int = 50
55
+ slack_thread_ts: Optional[str] = None
56
+ message_ts: Optional[str] = None
57
+ created_at: datetime = field(default_factory=datetime.now)
58
+ started_at: Optional[datetime] = None
59
+ completed_at: Optional[datetime] = None
60
+
61
+
62
+ @dataclass
63
+ class WorkflowResult:
64
+ """Result of a complete workflow execution."""
65
+
66
+ task: AgentTask
67
+ success: bool
68
+ plan: Optional[str] = None
69
+ work_output: Optional[str] = None
70
+ evaluation: Optional[str] = None
71
+ eval_result: Optional[EvalResult] = None
72
+ total_turns: int = 0
73
+ duration_ms: Optional[int] = None
74
+
75
+
76
+ class MultiAgentOrchestrator:
77
+ """Orchestrates multi-agent workflows.
78
+
79
+ Runs tasks through Planner -> Worker -> Evaluator pipeline.
80
+ Each agent uses its own isolated subprocess session.
81
+ """
82
+
83
+ def __init__(
84
+ self,
85
+ executor: SubprocessExecutor,
86
+ max_iterations: int = 3,
87
+ ) -> None:
88
+ """Initialize orchestrator.
89
+
90
+ Args:
91
+ executor: Claude executor for running agents
92
+ max_iterations: Max worker-evaluator iterations
93
+ """
94
+ self.executor = executor
95
+ self.max_iterations = max_iterations
96
+ self._active_tasks: dict[str, AgentTask] = {}
97
+
98
+ async def execute_workflow(
99
+ self,
100
+ task: AgentTask,
101
+ on_status_update: Optional[Callable[[AgentTask], Awaitable[None]]] = None,
102
+ ) -> WorkflowResult:
103
+ """Execute the full multi-agent workflow.
104
+
105
+ Args:
106
+ task: The task to execute
107
+ on_status_update: Optional callback for status updates
108
+
109
+ Returns:
110
+ WorkflowResult with outcomes
111
+ """
112
+ self._active_tasks[task.task_id] = task
113
+ task.started_at = datetime.now()
114
+ start_time = asyncio.get_running_loop().time()
115
+
116
+ try:
117
+ # Phase 1: Planning
118
+ task.status = TaskStatus.PLANNING
119
+ if on_status_update:
120
+ await on_status_update(task)
121
+
122
+ plan_result = await self._run_planner(task)
123
+ if not plan_result.success:
124
+ task.status = TaskStatus.FAILED
125
+ task.error_message = plan_result.error
126
+ return self._create_result(task, False, start_time)
127
+
128
+ task.plan_output = plan_result.output
129
+ task.turn_count += 1
130
+
131
+ # Phase 2: Working (with potential re-iterations)
132
+ for iteration in range(self.max_iterations):
133
+ task.status = TaskStatus.WORKING
134
+ if on_status_update:
135
+ await on_status_update(task)
136
+
137
+ work_result = await self._run_worker(task)
138
+ if not work_result.success:
139
+ task.status = TaskStatus.FAILED
140
+ task.error_message = work_result.error
141
+ return self._create_result(task, False, start_time)
142
+
143
+ task.work_output = work_result.output
144
+ task.turn_count += 1
145
+
146
+ # Phase 3: Evaluation
147
+ task.status = TaskStatus.EVALUATING
148
+ if on_status_update:
149
+ await on_status_update(task)
150
+
151
+ eval_result = await self._run_evaluator(task)
152
+ task.eval_output = eval_result.output
153
+ task.turn_count += 1
154
+
155
+ # Parse evaluation result
156
+ eval_verdict = self._parse_eval_result(eval_result.output)
157
+ task.eval_result = eval_verdict
158
+
159
+ if eval_verdict == EvalResult.COMPLETE:
160
+ task.status = TaskStatus.COMPLETED
161
+ task.completed_at = datetime.now()
162
+ return self._create_result(task, True, start_time)
163
+
164
+ elif eval_verdict == EvalResult.FAILED:
165
+ task.status = TaskStatus.FAILED
166
+ task.error_message = "Evaluation returned FAILED"
167
+ return self._create_result(task, False, start_time)
168
+
169
+ # For PARTIAL or INCOMPLETE, continue to next iteration
170
+ logger.info(
171
+ f"Task {task.task_id} eval: {eval_verdict.value}, "
172
+ f"iteration {iteration + 1}/{self.max_iterations}"
173
+ )
174
+
175
+ # Max iterations reached
176
+ task.status = TaskStatus.COMPLETED
177
+ task.completed_at = datetime.now()
178
+ return self._create_result(task, True, start_time)
179
+
180
+ except asyncio.CancelledError:
181
+ task.status = TaskStatus.CANCELLED
182
+ raise
183
+
184
+ except Exception as e:
185
+ task.status = TaskStatus.FAILED
186
+ task.error_message = str(e)
187
+ logger.error(f"Workflow failed for task {task.task_id}: {e}")
188
+ return self._create_result(task, False, start_time)
189
+
190
+ finally:
191
+ self._active_tasks.pop(task.task_id, None)
192
+ # Cleanup agent sessions
193
+ await self._cleanup_sessions(task.task_id)
194
+
195
+ async def _run_planner(self, task: AgentTask) -> ExecutionResult:
196
+ """Run the planner agent."""
197
+ prompt = format_task_prompt(
198
+ role=AgentRole.PLANNER,
199
+ task=task.description,
200
+ working_directory=task.working_directory,
201
+ )
202
+
203
+ # Use task-specific session for planner
204
+ session_id = f"{task.task_id}-planner"
205
+
206
+ return await self.executor.execute(
207
+ prompt=prompt,
208
+ working_directory=task.working_directory,
209
+ session_id=session_id,
210
+ execution_id=session_id,
211
+ )
212
+
213
+ async def _run_worker(self, task: AgentTask) -> ExecutionResult:
214
+ """Run the worker agent."""
215
+ prompt = format_task_prompt(
216
+ role=AgentRole.WORKER,
217
+ task=task.description,
218
+ plan=task.plan_output,
219
+ )
220
+
221
+ # Use task-specific session for worker
222
+ session_id = f"{task.task_id}-worker"
223
+
224
+ return await self.executor.execute(
225
+ prompt=prompt,
226
+ working_directory=task.working_directory,
227
+ session_id=session_id,
228
+ execution_id=session_id,
229
+ )
230
+
231
+ async def _run_evaluator(self, task: AgentTask) -> ExecutionResult:
232
+ """Run the evaluator agent."""
233
+ prompt = format_task_prompt(
234
+ role=AgentRole.EVALUATOR,
235
+ task=task.description,
236
+ plan=task.plan_output,
237
+ work_output=task.work_output,
238
+ )
239
+
240
+ # Use task-specific session for evaluator
241
+ session_id = f"{task.task_id}-evaluator"
242
+
243
+ return await self.executor.execute(
244
+ prompt=prompt,
245
+ working_directory=task.working_directory,
246
+ session_id=session_id,
247
+ execution_id=session_id,
248
+ )
249
+
250
+ def _parse_eval_result(self, output: str) -> EvalResult:
251
+ """Parse evaluation verdict from output."""
252
+ output_upper = output.upper()
253
+
254
+ if "COMPLETE" in output_upper and "INCOMPLETE" not in output_upper:
255
+ return EvalResult.COMPLETE
256
+ elif "INCOMPLETE" in output_upper:
257
+ return EvalResult.INCOMPLETE
258
+ elif "PARTIAL" in output_upper:
259
+ return EvalResult.PARTIAL
260
+ elif "FAILED" in output_upper or "FAIL" in output_upper:
261
+ return EvalResult.FAILED
262
+
263
+ # Default to partial if can't determine
264
+ return EvalResult.PARTIAL
265
+
266
+ def _create_result(
267
+ self,
268
+ task: AgentTask,
269
+ success: bool,
270
+ start_time: float,
271
+ ) -> WorkflowResult:
272
+ """Create a workflow result."""
273
+ duration_ms = int((asyncio.get_running_loop().time() - start_time) * 1000)
274
+
275
+ return WorkflowResult(
276
+ task=task,
277
+ success=success,
278
+ plan=task.plan_output,
279
+ work_output=task.work_output,
280
+ evaluation=task.eval_output,
281
+ eval_result=task.eval_result,
282
+ total_turns=task.turn_count,
283
+ duration_ms=duration_ms,
284
+ )
285
+
286
+ async def _cleanup_sessions(self, task_id: str) -> None:
287
+ """Cancel any active subprocess executions for a task."""
288
+ for role in ["planner", "worker", "evaluator"]:
289
+ session_id = f"{task_id}-{role}"
290
+ await self.executor.cancel(session_id)
291
+
292
+ async def cancel_task(self, task_id: str) -> bool:
293
+ """Cancel an active task.
294
+
295
+ Args:
296
+ task_id: The task ID to cancel
297
+
298
+ Returns:
299
+ True if task was found and cancelled
300
+ """
301
+ task = self._active_tasks.get(task_id)
302
+ if not task:
303
+ return False
304
+
305
+ task.status = TaskStatus.CANCELLED
306
+ await self._cleanup_sessions(task_id)
307
+ return True
308
+
309
+ def get_active_tasks(self) -> list[AgentTask]:
310
+ """Get list of active tasks."""
311
+ return list(self._active_tasks.values())
312
+
313
+ def get_task(self, task_id: str) -> Optional[AgentTask]:
314
+ """Get a specific task by ID."""
315
+ return self._active_tasks.get(task_id)