devs-webhook 3.3.6__tar.gz → 3.4.1__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.
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/PKG-INFO +1 -3
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/README.md +0 -2
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/config.py +5 -9
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/claude_dispatcher.py +20 -7
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/container_pool.py +13 -13
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/main_cli.py +9 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/PKG-INFO +1 -3
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/pyproject.toml +1 -1
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/LICENSE +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/__init__.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/app.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/cli/__init__.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/cli/worker.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/__init__.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/base_dispatcher.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/deduplication.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/repository_manager.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/task_processor.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/test_dispatcher.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/webhook_config.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/webhook_handler.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/__init__.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/app_auth.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/client.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/models.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/parser.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/sources/__init__.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/sources/base.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/sources/sqs_source.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/sources/webhook_source.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/__init__.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/async_utils.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/container_logs.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/github.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/logging.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/s3_artifacts.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/serialization.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/SOURCES.txt +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/dependency_links.txt +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/entry_points.txt +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/requires.txt +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/top_level.txt +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/setup.cfg +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_allowlist.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_authentication.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_authorized_users.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_ci_container_pool.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_cleanup_mode.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_container_logs.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_single_queue.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_sqs_burst.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_stop_container_after_task.py +0 -0
- {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_webhook_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devs-webhook
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.1
|
|
4
4
|
Summary: GitHub webhook handler for automated devcontainer operations with Claude Code
|
|
5
5
|
Author: Dan Lester
|
|
6
6
|
License-Expression: MIT
|
|
@@ -107,7 +107,6 @@ export AUTHORIZED_TRIGGER_USERS="danlester,admin" # Users who can trigger proce
|
|
|
107
107
|
# Optional settings (with defaults)
|
|
108
108
|
export CONTAINER_POOL="eamonn,harry,darren"
|
|
109
109
|
export CONTAINER_TIMEOUT_MINUTES="30"
|
|
110
|
-
export MAX_CONCURRENT_TASKS="3"
|
|
111
110
|
export WEBHOOK_HOST="0.0.0.0"
|
|
112
111
|
export WEBHOOK_PORT="8000"
|
|
113
112
|
```
|
|
@@ -446,7 +445,6 @@ The webhook handler automatically detects and uses these settings when processin
|
|
|
446
445
|
| `AUTHORIZED_TRIGGER_USERS` | (empty - allows all) | Comma-separated list of users who can trigger events |
|
|
447
446
|
| `CONTAINER_POOL` | `eamonn,harry,darren` | Container names |
|
|
448
447
|
| `CONTAINER_TIMEOUT_MINUTES` | `30` | Container timeout |
|
|
449
|
-
| `MAX_CONCURRENT_TASKS` | `3` | Max parallel tasks |
|
|
450
448
|
| `REPO_CACHE_DIR` | `~/.devs-webhook/repos` | Repository cache |
|
|
451
449
|
| `WORKSPACE_DIR` | `~/.devs-webhook/workspaces` | Container workspaces |
|
|
452
450
|
| `WEBHOOK_HOST` | `0.0.0.0` | Server host |
|
|
@@ -50,7 +50,6 @@ export AUTHORIZED_TRIGGER_USERS="danlester,admin" # Users who can trigger proce
|
|
|
50
50
|
# Optional settings (with defaults)
|
|
51
51
|
export CONTAINER_POOL="eamonn,harry,darren"
|
|
52
52
|
export CONTAINER_TIMEOUT_MINUTES="30"
|
|
53
|
-
export MAX_CONCURRENT_TASKS="3"
|
|
54
53
|
export WEBHOOK_HOST="0.0.0.0"
|
|
55
54
|
export WEBHOOK_PORT="8000"
|
|
56
55
|
```
|
|
@@ -389,7 +388,6 @@ The webhook handler automatically detects and uses these settings when processin
|
|
|
389
388
|
| `AUTHORIZED_TRIGGER_USERS` | (empty - allows all) | Comma-separated list of users who can trigger events |
|
|
390
389
|
| `CONTAINER_POOL` | `eamonn,harry,darren` | Container names |
|
|
391
390
|
| `CONTAINER_TIMEOUT_MINUTES` | `30` | Container timeout |
|
|
392
|
-
| `MAX_CONCURRENT_TASKS` | `3` | Max parallel tasks |
|
|
393
391
|
| `REPO_CACHE_DIR` | `~/.devs-webhook/repos` | Repository cache |
|
|
394
392
|
| `WORKSPACE_DIR` | `~/.devs-webhook/workspaces` | Container workspaces |
|
|
395
393
|
| `WEBHOOK_HOST` | `0.0.0.0` | Server host |
|
|
@@ -90,8 +90,6 @@ class WebhookConfig(BaseSettings, BaseConfig):
|
|
|
90
90
|
"This ensures only one running container per dev name at any time, "
|
|
91
91
|
"reducing RAM usage when multiple repos are in play."
|
|
92
92
|
)
|
|
93
|
-
max_concurrent_tasks: int = Field(default=3, description="Maximum concurrent tasks")
|
|
94
|
-
|
|
95
93
|
# Repository settings
|
|
96
94
|
repo_cache_dir: Path = Field(
|
|
97
95
|
default_factory=lambda: Path.home() / ".devs" / "repocache",
|
|
@@ -279,18 +277,16 @@ class WebhookConfig(BaseSettings, BaseConfig):
|
|
|
279
277
|
missing.append("github_webhook_secret (GITHUB_WEBHOOK_SECRET) - required for signature verification")
|
|
280
278
|
|
|
281
279
|
# Task source specific validations
|
|
282
|
-
if self.task_source == "
|
|
283
|
-
# Require admin password in production mode
|
|
284
|
-
if not self.dev_mode and not self.admin_password:
|
|
285
|
-
missing.append("admin_password (ADMIN_PASSWORD) - required in production mode")
|
|
286
|
-
|
|
287
|
-
elif self.task_source == "sqs":
|
|
280
|
+
if self.task_source == "sqs":
|
|
288
281
|
# SQS source requires queue URL
|
|
289
282
|
if not self.aws_sqs_queue_url:
|
|
290
283
|
missing.append("aws_sqs_queue_url (AWS_SQS_QUEUE_URL) - required for SQS source")
|
|
291
284
|
|
|
292
|
-
|
|
285
|
+
elif self.task_source != "webhook":
|
|
293
286
|
missing.append(f"task_source must be 'webhook' or 'sqs', got '{self.task_source}'")
|
|
287
|
+
# admin_password is only enforced when actually starting the FastAPI server
|
|
288
|
+
# (see main_cli.serve). It's not required for SQS-source operation or for
|
|
289
|
+
# read-only commands like `devs-webhook config`.
|
|
294
290
|
|
|
295
291
|
# Raise error if any required settings are missing
|
|
296
292
|
if missing:
|
|
@@ -176,8 +176,25 @@ class ClaudeDispatcher(BaseDispatcher):
|
|
|
176
176
|
logger.info("Created new workspace",
|
|
177
177
|
container=dev_name,
|
|
178
178
|
workspace_dir=str(workspace_dir))
|
|
179
|
-
|
|
180
|
-
# 3.
|
|
179
|
+
|
|
180
|
+
# 3. Ensure container is running with environment variables from DEVS.yml.
|
|
181
|
+
# check_rebuild defaults to True so the image is rebuilt when devcontainer
|
|
182
|
+
# files change. Safe here because the per-devname queue is serial — a rebuild
|
|
183
|
+
# only happens between tasks, never mid-task.
|
|
184
|
+
extra_env = None
|
|
185
|
+
if devs_options:
|
|
186
|
+
extra_env = devs_options.get_env_vars(dev_name)
|
|
187
|
+
|
|
188
|
+
if not container_manager.ensure_container_running(
|
|
189
|
+
dev_name=dev_name,
|
|
190
|
+
workspace_dir=workspace_dir,
|
|
191
|
+
force_rebuild=False,
|
|
192
|
+
debug=self.config.dev_mode,
|
|
193
|
+
extra_env=extra_env,
|
|
194
|
+
):
|
|
195
|
+
return False, "", f"Failed to start container for {dev_name}", 1
|
|
196
|
+
|
|
197
|
+
# 4. Build Claude prompt
|
|
181
198
|
workspace_name = project.get_workspace_name(dev_name)
|
|
182
199
|
workspace_path = f"/workspaces/{workspace_name}"
|
|
183
200
|
repo_name = event.repository.full_name
|
|
@@ -277,14 +294,10 @@ Always remember to PUSH your work to origin!
|
|
|
277
294
|
prompt_length=len(prompt),
|
|
278
295
|
event_type="PR" if is_pr else "Issue")
|
|
279
296
|
|
|
280
|
-
#
|
|
297
|
+
# 5. Execute Claude (like CLI pattern) with environment variables from DEVS.yml
|
|
281
298
|
logger.info("Executing Claude via ContainerManager (like CLI)",
|
|
282
299
|
container=dev_name)
|
|
283
300
|
|
|
284
|
-
extra_env = None
|
|
285
|
-
if devs_options:
|
|
286
|
-
extra_env = devs_options.get_env_vars(dev_name)
|
|
287
|
-
|
|
288
301
|
# Start container logging if enabled
|
|
289
302
|
if container_log:
|
|
290
303
|
container_log.start(prompt=prompt, workspace_dir=str(workspace_dir))
|
|
@@ -369,6 +369,16 @@ class ContainerPool:
|
|
|
369
369
|
# Determine which container to use
|
|
370
370
|
best_container = None
|
|
371
371
|
|
|
372
|
+
# Load score = queued tasks + 1 if a task is currently running on the
|
|
373
|
+
# container. Without the running-task term, qsize() reads 0 for a
|
|
374
|
+
# busy-but-not-backlogged container (the worker pulls off the queue
|
|
375
|
+
# immediately) and the first item in target_pool wins every tie,
|
|
376
|
+
# pinning all traffic to one devname.
|
|
377
|
+
def _load(dev_name: str) -> int:
|
|
378
|
+
queued = self.container_queues[dev_name].qsize()
|
|
379
|
+
running = 1 if dev_name in self.running_containers else 0
|
|
380
|
+
return queued + running
|
|
381
|
+
|
|
372
382
|
if single_queue_required:
|
|
373
383
|
# Use the previously assigned container for this single-queue repo
|
|
374
384
|
if repo_name in self.single_queue_assignments:
|
|
@@ -389,12 +399,7 @@ class ContainerPool:
|
|
|
389
399
|
|
|
390
400
|
if best_container is None:
|
|
391
401
|
# First time for this single-queue repo or need reassignment
|
|
392
|
-
|
|
393
|
-
for dev_name in target_pool:
|
|
394
|
-
queue_size = self.container_queues[dev_name].qsize()
|
|
395
|
-
if queue_size < min_queue_size:
|
|
396
|
-
min_queue_size = queue_size
|
|
397
|
-
best_container = dev_name
|
|
402
|
+
best_container = min(target_pool, key=_load)
|
|
398
403
|
|
|
399
404
|
if best_container:
|
|
400
405
|
self.single_queue_assignments[repo_name] = best_container
|
|
@@ -403,13 +408,8 @@ class ContainerPool:
|
|
|
403
408
|
container=best_container,
|
|
404
409
|
task_type=task_type)
|
|
405
410
|
else:
|
|
406
|
-
# Normal load balancing -
|
|
407
|
-
|
|
408
|
-
for dev_name in target_pool:
|
|
409
|
-
queue_size = self.container_queues[dev_name].qsize()
|
|
410
|
-
if queue_size < min_queue_size:
|
|
411
|
-
min_queue_size = queue_size
|
|
412
|
-
best_container = dev_name
|
|
411
|
+
# Normal load balancing - lowest queued+running count in target pool
|
|
412
|
+
best_container = min(target_pool, key=_load) if target_pool else None
|
|
413
413
|
|
|
414
414
|
if best_container is None:
|
|
415
415
|
logger.error("No containers available for task queuing",
|
|
@@ -124,6 +124,15 @@ def serve(host: str, port: int, reload: bool, env_file: Path, dev: bool, source:
|
|
|
124
124
|
|
|
125
125
|
# Start the appropriate task source
|
|
126
126
|
if config.task_source == "webhook":
|
|
127
|
+
# admin_password is required only when actually serving the FastAPI app
|
|
128
|
+
# (the admin endpoints use BasicAuth). SQS-source operation doesn't need it.
|
|
129
|
+
if not config.dev_mode and not config.admin_password:
|
|
130
|
+
click.echo(
|
|
131
|
+
"❌ ADMIN_PASSWORD is required when serving the webhook FastAPI app "
|
|
132
|
+
"in production mode (set ADMIN_PASSWORD or enable DEV_MODE=true)."
|
|
133
|
+
)
|
|
134
|
+
exit(1)
|
|
135
|
+
|
|
127
136
|
# Override config with CLI options
|
|
128
137
|
actual_host = host or config.webhook_host
|
|
129
138
|
actual_port = port or config.webhook_port
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devs-webhook
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.1
|
|
4
4
|
Summary: GitHub webhook handler for automated devcontainer operations with Claude Code
|
|
5
5
|
Author: Dan Lester
|
|
6
6
|
License-Expression: MIT
|
|
@@ -107,7 +107,6 @@ export AUTHORIZED_TRIGGER_USERS="danlester,admin" # Users who can trigger proce
|
|
|
107
107
|
# Optional settings (with defaults)
|
|
108
108
|
export CONTAINER_POOL="eamonn,harry,darren"
|
|
109
109
|
export CONTAINER_TIMEOUT_MINUTES="30"
|
|
110
|
-
export MAX_CONCURRENT_TASKS="3"
|
|
111
110
|
export WEBHOOK_HOST="0.0.0.0"
|
|
112
111
|
export WEBHOOK_PORT="8000"
|
|
113
112
|
```
|
|
@@ -446,7 +445,6 @@ The webhook handler automatically detects and uses these settings when processin
|
|
|
446
445
|
| `AUTHORIZED_TRIGGER_USERS` | (empty - allows all) | Comma-separated list of users who can trigger events |
|
|
447
446
|
| `CONTAINER_POOL` | `eamonn,harry,darren` | Container names |
|
|
448
447
|
| `CONTAINER_TIMEOUT_MINUTES` | `30` | Container timeout |
|
|
449
|
-
| `MAX_CONCURRENT_TASKS` | `3` | Max parallel tasks |
|
|
450
448
|
| `REPO_CACHE_DIR` | `~/.devs-webhook/repos` | Repository cache |
|
|
451
449
|
| `WORKSPACE_DIR` | `~/.devs-webhook/workspaces` | Container workspaces |
|
|
452
450
|
| `WEBHOOK_HOST` | `0.0.0.0` | Server host |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|