airut 0.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.
- airut-0.8.0/.airut/README.md +81 -0
- airut-0.8.0/.airut/airut.yaml +39 -0
- airut-0.8.0/.airut/container/Dockerfile +49 -0
- airut-0.8.0/.airut/container/gitconfig +9 -0
- airut-0.8.0/.airut/network-allowlist.yaml +59 -0
- airut-0.8.0/.claude/settings.json +4 -0
- airut-0.8.0/.github/workflows/code.yml +35 -0
- airut-0.8.0/.github/workflows/e2e.yml +26 -0
- airut-0.8.0/.github/workflows/publish.yml +33 -0
- airut-0.8.0/.github/workflows/security.yml +22 -0
- airut-0.8.0/.gitignore +11 -0
- airut-0.8.0/.mdformat.toml +4 -0
- airut-0.8.0/CLAUDE.md +347 -0
- airut-0.8.0/LICENSE +19 -0
- airut-0.8.0/PKG-INFO +261 -0
- airut-0.8.0/README.md +230 -0
- airut-0.8.0/SECURITY.md +21 -0
- airut-0.8.0/assets/logo-dark.svg +131 -0
- airut-0.8.0/assets/logo-square-transparent-bg.png +0 -0
- airut-0.8.0/assets/logo-square-white-bg.png +0 -0
- airut-0.8.0/assets/logo-transparent-bg.png +0 -0
- airut-0.8.0/assets/logo-white-bg.png +0 -0
- airut-0.8.0/assets/logo.svg +131 -0
- airut-0.8.0/config/airut.example.yaml +198 -0
- airut-0.8.0/doc/README.md +24 -0
- airut-0.8.0/doc/agentic-operation.md +302 -0
- airut-0.8.0/doc/architecture.md +328 -0
- airut-0.8.0/doc/deployment.md +635 -0
- airut-0.8.0/doc/execution-sandbox.md +129 -0
- airut-0.8.0/doc/gerrit-onboarding.md +218 -0
- airut-0.8.0/doc/m365-oauth2.md +227 -0
- airut-0.8.0/doc/network-sandbox.md +496 -0
- airut-0.8.0/doc/repo-onboarding.md +333 -0
- airut-0.8.0/doc/security.md +382 -0
- airut-0.8.0/lib/__init__.py +8 -0
- airut-0.8.0/lib/_bundled/__init__.py +12 -0
- airut-0.8.0/lib/_bundled/assets/__init__.py +6 -0
- airut-0.8.0/lib/_bundled/assets/logo.svg +131 -0
- airut-0.8.0/lib/_bundled/proxy/__init__.py +6 -0
- airut-0.8.0/lib/_bundled/proxy/aws_signing.py +1085 -0
- airut-0.8.0/lib/_bundled/proxy/dns_responder.py +230 -0
- airut-0.8.0/lib/_bundled/proxy/proxy-entrypoint.sh +69 -0
- airut-0.8.0/lib/_bundled/proxy/proxy.dockerfile +11 -0
- airut-0.8.0/lib/_bundled/proxy/proxy_filter.py +882 -0
- airut-0.8.0/lib/_version.py +5 -0
- airut-0.8.0/lib/airut.py +419 -0
- airut-0.8.0/lib/allowlist.py +126 -0
- airut-0.8.0/lib/claude_output/__init__.py +56 -0
- airut-0.8.0/lib/claude_output/extract.py +194 -0
- airut-0.8.0/lib/claude_output/parser.py +205 -0
- airut-0.8.0/lib/claude_output/types.py +124 -0
- airut-0.8.0/lib/conversation/__init__.py +38 -0
- airut-0.8.0/lib/conversation/conversation_layout.py +105 -0
- airut-0.8.0/lib/conversation/conversation_store.py +404 -0
- airut-0.8.0/lib/dashboard/__init__.py +51 -0
- airut-0.8.0/lib/dashboard/formatters.py +75 -0
- airut-0.8.0/lib/dashboard/handlers.py +908 -0
- airut-0.8.0/lib/dashboard/server.py +276 -0
- airut-0.8.0/lib/dashboard/sse.py +436 -0
- airut-0.8.0/lib/dashboard/tracker.py +448 -0
- airut-0.8.0/lib/dashboard/versioned.py +108 -0
- airut-0.8.0/lib/dashboard/views/__init__.py +70 -0
- airut-0.8.0/lib/dashboard/views/actions.py +765 -0
- airut-0.8.0/lib/dashboard/views/components.py +522 -0
- airut-0.8.0/lib/dashboard/views/dashboard.py +338 -0
- airut-0.8.0/lib/dashboard/views/network.py +231 -0
- airut-0.8.0/lib/dashboard/views/repo_detail.py +162 -0
- airut-0.8.0/lib/dashboard/views/styles.py +1038 -0
- airut-0.8.0/lib/dashboard/views/task_detail.py +309 -0
- airut-0.8.0/lib/dns.py +71 -0
- airut-0.8.0/lib/gateway/__init__.py +83 -0
- airut-0.8.0/lib/gateway/config.py +1555 -0
- airut-0.8.0/lib/gateway/conversation.py +257 -0
- airut-0.8.0/lib/gateway/dotenv_loader.py +72 -0
- airut-0.8.0/lib/gateway/listener.py +510 -0
- airut-0.8.0/lib/gateway/microsoft_oauth2.py +112 -0
- airut-0.8.0/lib/gateway/parsing.py +326 -0
- airut-0.8.0/lib/gateway/responder.py +255 -0
- airut-0.8.0/lib/gateway/security.py +390 -0
- airut-0.8.0/lib/gateway/service/__init__.py +48 -0
- airut-0.8.0/lib/gateway/service/email_replies.py +348 -0
- airut-0.8.0/lib/gateway/service/gateway.py +740 -0
- airut-0.8.0/lib/gateway/service/message_processing.py +732 -0
- airut-0.8.0/lib/gateway/service/repo_handler.py +330 -0
- airut-0.8.0/lib/gateway/service/usage_stats.py +105 -0
- airut-0.8.0/lib/gh/__init__.py +48 -0
- airut-0.8.0/lib/gh/ci.py +403 -0
- airut-0.8.0/lib/gh/pr.py +192 -0
- airut-0.8.0/lib/gh/review.py +350 -0
- airut-0.8.0/lib/git_mirror.py +495 -0
- airut-0.8.0/lib/git_version.py +211 -0
- airut-0.8.0/lib/html_to_text.py +545 -0
- airut-0.8.0/lib/install_services.py +251 -0
- airut-0.8.0/lib/logging.py +138 -0
- airut-0.8.0/lib/markdown.py +385 -0
- airut-0.8.0/lib/sandbox/__init__.py +71 -0
- airut-0.8.0/lib/sandbox/_entrypoint.py +41 -0
- airut-0.8.0/lib/sandbox/_image.py +221 -0
- airut-0.8.0/lib/sandbox/_network.py +85 -0
- airut-0.8.0/lib/sandbox/_output.py +139 -0
- airut-0.8.0/lib/sandbox/_proxy.py +728 -0
- airut-0.8.0/lib/sandbox/event_log.py +167 -0
- airut-0.8.0/lib/sandbox/network_log.py +84 -0
- airut-0.8.0/lib/sandbox/sandbox.py +220 -0
- airut-0.8.0/lib/sandbox/secrets.py +293 -0
- airut-0.8.0/lib/sandbox/task.py +434 -0
- airut-0.8.0/lib/sandbox/types.py +96 -0
- airut-0.8.0/pyproject.toml +114 -0
- airut-0.8.0/scripts/.gitkeep +0 -0
- airut-0.8.0/scripts/airut.py +23 -0
- airut-0.8.0/scripts/check_markdown.py +80 -0
- airut-0.8.0/scripts/ci.py +316 -0
- airut-0.8.0/scripts/hatch_build.py +113 -0
- airut-0.8.0/scripts/install_services.py +198 -0
- airut-0.8.0/scripts/pr.py +399 -0
- airut-0.8.0/spec/README.md +47 -0
- airut-0.8.0/spec/authentication.md +172 -0
- airut-0.8.0/spec/aws-sigv4-resigning.md +817 -0
- airut-0.8.0/spec/dashboard.md +247 -0
- airut-0.8.0/spec/gateway-architecture.md +501 -0
- airut-0.8.0/spec/image.md +146 -0
- airut-0.8.0/spec/integration-tests.md +46 -0
- airut-0.8.0/spec/live-dashboard.md +187 -0
- airut-0.8.0/spec/local-ci-runner.md +120 -0
- airut-0.8.0/spec/masked-secrets.md +246 -0
- airut-0.8.0/spec/multi-repo.md +316 -0
- airut-0.8.0/spec/network-sandbox.md +144 -0
- airut-0.8.0/spec/pr-workflow-tool.md +233 -0
- airut-0.8.0/spec/repo-config.md +121 -0
- airut-0.8.0/spec/sandbox.md +193 -0
- airut-0.8.0/tests/__init__.py +6 -0
- airut-0.8.0/tests/claude_output/__init__.py +4 -0
- airut-0.8.0/tests/claude_output/test_extract.py +329 -0
- airut-0.8.0/tests/claude_output/test_parser.py +370 -0
- airut-0.8.0/tests/claude_output/test_pipeline.py +815 -0
- airut-0.8.0/tests/claude_output/test_types.py +129 -0
- airut-0.8.0/tests/conftest.py +188 -0
- airut-0.8.0/tests/conversation/__init__.py +4 -0
- airut-0.8.0/tests/conversation/conftest.py +6 -0
- airut-0.8.0/tests/conversation/test_conversation_layout.py +98 -0
- airut-0.8.0/tests/conversation/test_conversation_store.py +542 -0
- airut-0.8.0/tests/dashboard/__init__.py +5 -0
- airut-0.8.0/tests/dashboard/conftest.py +173 -0
- airut-0.8.0/tests/dashboard/test_actions.py +1066 -0
- airut-0.8.0/tests/dashboard/test_formatters.py +95 -0
- airut-0.8.0/tests/dashboard/test_handlers.py +1701 -0
- airut-0.8.0/tests/dashboard/test_network.py +307 -0
- airut-0.8.0/tests/dashboard/test_server.py +1180 -0
- airut-0.8.0/tests/dashboard/test_sse.py +937 -0
- airut-0.8.0/tests/dashboard/test_tracker.py +611 -0
- airut-0.8.0/tests/dashboard/test_versioned.py +216 -0
- airut-0.8.0/tests/gateway/__init__.py +5 -0
- airut-0.8.0/tests/gateway/conftest.py +84 -0
- airut-0.8.0/tests/gateway/service/__init__.py +5 -0
- airut-0.8.0/tests/gateway/service/conftest.py +118 -0
- airut-0.8.0/tests/gateway/service/test_email_replies.py +369 -0
- airut-0.8.0/tests/gateway/service/test_gateway.py +1116 -0
- airut-0.8.0/tests/gateway/service/test_message_processing.py +1282 -0
- airut-0.8.0/tests/gateway/service/test_repo_handler.py +404 -0
- airut-0.8.0/tests/gateway/service/test_usage_stats.py +392 -0
- airut-0.8.0/tests/gateway/test_config.py +2463 -0
- airut-0.8.0/tests/gateway/test_conversation.py +326 -0
- airut-0.8.0/tests/gateway/test_dotenv_loader.py +141 -0
- airut-0.8.0/tests/gateway/test_integration.py +1660 -0
- airut-0.8.0/tests/gateway/test_listener.py +1249 -0
- airut-0.8.0/tests/gateway/test_microsoft_oauth2.py +150 -0
- airut-0.8.0/tests/gateway/test_parsing.py +678 -0
- airut-0.8.0/tests/gateway/test_responder.py +584 -0
- airut-0.8.0/tests/gateway/test_security.py +685 -0
- airut-0.8.0/tests/integration/__init__.py +5 -0
- airut-0.8.0/tests/integration/gateway/__init__.py +5 -0
- airut-0.8.0/tests/integration/gateway/conftest.py +280 -0
- airut-0.8.0/tests/integration/gateway/email_server.py +771 -0
- airut-0.8.0/tests/integration/gateway/environment.py +354 -0
- airut-0.8.0/tests/integration/gateway/mock_claude.py +248 -0
- airut-0.8.0/tests/integration/gateway/mock_podman.py +369 -0
- airut-0.8.0/tests/integration/gateway/mock_podman_wrapper.sh +4 -0
- airut-0.8.0/tests/integration/gateway/test_attachments.py +297 -0
- airut-0.8.0/tests/integration/gateway/test_authorization.py +255 -0
- airut-0.8.0/tests/integration/gateway/test_conversation_resume.py +289 -0
- airut-0.8.0/tests/integration/gateway/test_errors.py +204 -0
- airut-0.8.0/tests/integration/gateway/test_git_mirror_updates.py +571 -0
- airut-0.8.0/tests/integration/gateway/test_multi_repo.py +458 -0
- airut-0.8.0/tests/integration/gateway/test_multi_sender.py +336 -0
- airut-0.8.0/tests/integration/gateway/test_new_conversation.py +249 -0
- airut-0.8.0/tests/integration/gateway/test_outbox.py +497 -0
- airut-0.8.0/tests/integration/gateway/test_repo_init_failures.py +405 -0
- airut-0.8.0/tests/integration/gateway/test_streaming_and_stop.py +389 -0
- airut-0.8.0/tests/proxy/conftest.py +136 -0
- airut-0.8.0/tests/proxy/test_aws_signing.py +1388 -0
- airut-0.8.0/tests/proxy/test_dns_responder.py +500 -0
- airut-0.8.0/tests/proxy/test_proxy_allowlist.py +547 -0
- airut-0.8.0/tests/proxy/test_proxy_filter.py +2050 -0
- airut-0.8.0/tests/proxy/vectors.py +57 -0
- airut-0.8.0/tests/sandbox/__init__.py +0 -0
- airut-0.8.0/tests/sandbox/conftest.py +102 -0
- airut-0.8.0/tests/sandbox/test_entrypoint.py +60 -0
- airut-0.8.0/tests/sandbox/test_event_log.py +297 -0
- airut-0.8.0/tests/sandbox/test_image.py +370 -0
- airut-0.8.0/tests/sandbox/test_network.py +101 -0
- airut-0.8.0/tests/sandbox/test_network_log.py +180 -0
- airut-0.8.0/tests/sandbox/test_output.py +355 -0
- airut-0.8.0/tests/sandbox/test_proxy.py +1005 -0
- airut-0.8.0/tests/sandbox/test_sandbox.py +337 -0
- airut-0.8.0/tests/sandbox/test_secrets.py +462 -0
- airut-0.8.0/tests/sandbox/test_task.py +686 -0
- airut-0.8.0/tests/test_airut.py +509 -0
- airut-0.8.0/tests/test_allowlist.py +262 -0
- airut-0.8.0/tests/test_ci.py +401 -0
- airut-0.8.0/tests/test_dns.py +91 -0
- airut-0.8.0/tests/test_git_mirror.py +775 -0
- airut-0.8.0/tests/test_hatch_build.py +171 -0
- airut-0.8.0/tests/test_html_to_text.py +1013 -0
- airut-0.8.0/tests/test_install_services.py +432 -0
- airut-0.8.0/tests/test_lib/__init__.py +6 -0
- airut-0.8.0/tests/test_lib/test_gh/__init__.py +6 -0
- airut-0.8.0/tests/test_lib/test_gh/test_ci.py +623 -0
- airut-0.8.0/tests/test_lib/test_gh/test_pr.py +297 -0
- airut-0.8.0/tests/test_lib/test_gh/test_review.py +673 -0
- airut-0.8.0/tests/test_lib/test_git_version.py +352 -0
- airut-0.8.0/tests/test_lib/test_logging.py +206 -0
- airut-0.8.0/tests/test_markdown.py +579 -0
- airut-0.8.0/tests/test_scripts/__init__.py +5 -0
- airut-0.8.0/tests/test_scripts/test_pr.py +775 -0
- airut-0.8.0/uv.lock +974 -0
- airut-0.8.0/workflows/release.md +117 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Airut Repo Configuration
|
|
2
|
+
|
|
3
|
+
This directory contains repo-specific Airut configuration. Files here are read
|
|
4
|
+
from the git mirror's default branch at task start, so changes take effect after
|
|
5
|
+
merging to main without server restart.
|
|
6
|
+
|
|
7
|
+
For a minimal working example of `.airut/` configuration, see the
|
|
8
|
+
[airut.org website repository](https://github.com/airutorg/website).
|
|
9
|
+
|
|
10
|
+
## Files
|
|
11
|
+
|
|
12
|
+
### `airut.yaml` — Repo Config
|
|
13
|
+
|
|
14
|
+
Controls repo-specific behavior:
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
default_model: opus # Claude model when not specified via subaddressing
|
|
18
|
+
timeout: 6000 # Max container execution time (seconds)
|
|
19
|
+
|
|
20
|
+
network:
|
|
21
|
+
sandbox_enabled: true # Enable network allowlist enforcement
|
|
22
|
+
|
|
23
|
+
container_env: # Environment variables for containers
|
|
24
|
+
GH_TOKEN: !secret GH_TOKEN # Required secret from server pool
|
|
25
|
+
API_KEY: !secret? API_KEY # Optional secret (skip if missing)
|
|
26
|
+
BUCKET_NAME: "my-bucket" # Inline value (non-secret)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**YAML Tags:**
|
|
30
|
+
|
|
31
|
+
- `!secret NAME` — resolve from server's secrets pool (error if missing)
|
|
32
|
+
- `!secret? NAME` — optional secret (skip entry if missing)
|
|
33
|
+
- `!env` is NOT allowed in repo config (security: prevents reading server env)
|
|
34
|
+
|
|
35
|
+
### `network-allowlist.yaml` — Network Sandbox
|
|
36
|
+
|
|
37
|
+
Defines which hosts containers can access. All HTTP(S) traffic is proxied and
|
|
38
|
+
checked against this allowlist. See `doc/network-sandbox.md`.
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
# Full-domain entries: all paths and methods allowed
|
|
42
|
+
domains:
|
|
43
|
+
- api.anthropic.com
|
|
44
|
+
- pypi.org
|
|
45
|
+
|
|
46
|
+
# URL prefix entries: host + path required, optional method filter
|
|
47
|
+
url_prefixes:
|
|
48
|
+
- host: api.github.com
|
|
49
|
+
path: /repos/owner/repo*
|
|
50
|
+
- host: api.github.com
|
|
51
|
+
path: /graphql
|
|
52
|
+
methods: [POST] # optional: restrict to specific HTTP methods
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Self-service workflow:** When the agent encounters a blocked request, it can
|
|
56
|
+
edit this file and submit a PR. A human must review and merge before the change
|
|
57
|
+
takes effect.
|
|
58
|
+
|
|
59
|
+
### `container/Dockerfile` — Container Image
|
|
60
|
+
|
|
61
|
+
Repo-defined base image. Controls what tools and dependencies are available in
|
|
62
|
+
the Claude Code container. See `spec/image.md`.
|
|
63
|
+
|
|
64
|
+
The server adds an overlay with the entrypoint script, so the repo Dockerfile
|
|
65
|
+
doesn't need to define `ENTRYPOINT`.
|
|
66
|
+
|
|
67
|
+
## Server Config
|
|
68
|
+
|
|
69
|
+
Server-side configuration lives in `config/airut.yaml` (not in this directory).
|
|
70
|
+
It handles:
|
|
71
|
+
|
|
72
|
+
- Mail server credentials (IMAP/SMTP) — **each repo needs a dedicated inbox**
|
|
73
|
+
- Authorized senders and trusted authserv_id
|
|
74
|
+
- Storage directory and git repo URL
|
|
75
|
+
- Secrets pool (values that `!secret` tags reference)
|
|
76
|
+
|
|
77
|
+
> **Note:** Airut treats the IMAP inbox as a work queue. It polls for messages,
|
|
78
|
+
> processes every email, and permanently deletes messages after processing.
|
|
79
|
+
> Never use a shared or personal email account.
|
|
80
|
+
|
|
81
|
+
See `spec/repo-config.md` for the full schema.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Repo-specific Airut configuration.
|
|
2
|
+
#
|
|
3
|
+
# Loaded from the git mirror (main branch) at the start of each task.
|
|
4
|
+
# Changes take effect after merge to main without server restart.
|
|
5
|
+
#
|
|
6
|
+
# Tags:
|
|
7
|
+
# !secret NAME - Inject a value from the server's secrets pool.
|
|
8
|
+
# Errors if the server doesn't export that name.
|
|
9
|
+
# !secret? NAME - Optional secret. Skip the entry if the server doesn't
|
|
10
|
+
# export that name (no error).
|
|
11
|
+
# !env is NOT allowed (cannot read server environment variables).
|
|
12
|
+
|
|
13
|
+
# Default Claude model when the sender does not specify one via
|
|
14
|
+
# subaddressing (e.g. airut+opus@example.com).
|
|
15
|
+
default_model: opus
|
|
16
|
+
|
|
17
|
+
# Maximum time in seconds a Claude container may run before being killed.
|
|
18
|
+
timeout: 6000
|
|
19
|
+
|
|
20
|
+
network:
|
|
21
|
+
# Enable the network sandbox (default: true).
|
|
22
|
+
# When enabled, containers are placed on an isolated network and all HTTP(S)
|
|
23
|
+
# traffic is routed through a proxy that enforces the network allowlist
|
|
24
|
+
# (see .airut/network-allowlist.yaml). Set to false for unrestricted
|
|
25
|
+
# network access (break-glass override).
|
|
26
|
+
sandbox_enabled: true
|
|
27
|
+
|
|
28
|
+
# Environment variables passed into every Claude Code container.
|
|
29
|
+
# Values can be inline strings or !secret references to the server pool.
|
|
30
|
+
# All resolved values are automatically redacted from service logs.
|
|
31
|
+
# Entries whose value resolves to empty are skipped.
|
|
32
|
+
container_env:
|
|
33
|
+
# Claude authentication (optional; OAuth token takes precedence if both set).
|
|
34
|
+
# Use !secret? so either or both can be absent from server config.
|
|
35
|
+
CLAUDE_CODE_OAUTH_TOKEN: !secret? CLAUDE_CODE_OAUTH_TOKEN
|
|
36
|
+
ANTHROPIC_API_KEY: !secret? ANTHROPIC_API_KEY
|
|
37
|
+
|
|
38
|
+
# GitHub token for git operations inside the container.
|
|
39
|
+
GH_TOKEN: !secret GH_TOKEN
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
FROM ubuntu:24.04
|
|
2
|
+
|
|
3
|
+
# Install system dependencies
|
|
4
|
+
RUN apt-get update && apt-get install -y \
|
|
5
|
+
curl \
|
|
6
|
+
git \
|
|
7
|
+
ca-certificates \
|
|
8
|
+
ripgrep \
|
|
9
|
+
librsvg2-bin \
|
|
10
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
11
|
+
|
|
12
|
+
# Install GitHub CLI
|
|
13
|
+
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
14
|
+
| dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
15
|
+
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
16
|
+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
|
17
|
+
| tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
|
|
18
|
+
&& apt-get update \
|
|
19
|
+
&& apt-get install -y gh
|
|
20
|
+
|
|
21
|
+
# Install Claude Code
|
|
22
|
+
# NOTE: Run from a temp directory to work around OOM bug in installer when run
|
|
23
|
+
# as root during container build (https://github.com/anthropics/claude-code/issues/22536)
|
|
24
|
+
RUN mkdir /tmp/claude-install && cd /tmp/claude-install \
|
|
25
|
+
&& curl -fsSL https://claude.ai/install.sh | bash \
|
|
26
|
+
&& rm -rf /tmp/claude-install
|
|
27
|
+
|
|
28
|
+
# Add ~/.local/bin to PATH — Airut requires `claude` to be in PATH
|
|
29
|
+
# (also needed for `uv` below)
|
|
30
|
+
ENV PATH="/root/.local/bin:$PATH"
|
|
31
|
+
|
|
32
|
+
# Install uv (to ~/.local/bin)
|
|
33
|
+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
34
|
+
|
|
35
|
+
# /workspace is a host mount (ext4) while the uv cache lives on the container
|
|
36
|
+
# overlay filesystem. Hardlinks cannot cross filesystem boundaries, so uv
|
|
37
|
+
# falls back to copies and prints a noisy warning on every invocation.
|
|
38
|
+
# Setting COPY mode up-front silences the warning.
|
|
39
|
+
ENV UV_LINK_MODE=copy
|
|
40
|
+
|
|
41
|
+
# Install Python 3.13 via uv
|
|
42
|
+
RUN uv python install 3.13
|
|
43
|
+
|
|
44
|
+
# Configure git with gh credential helper
|
|
45
|
+
# NOTE: Using COPY instead of heredoc because Podman's image builder
|
|
46
|
+
# incorrectly interprets lines starting with '[' as Dockerfile instructions
|
|
47
|
+
COPY gitconfig /root/.gitconfig
|
|
48
|
+
|
|
49
|
+
WORKDIR /workspace
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Network allowlist for Claude Code containers.
|
|
2
|
+
#
|
|
3
|
+
# Only hosts listed here are reachable from inside the container.
|
|
4
|
+
# All other traffic is blocked by the mitmproxy-based network proxy.
|
|
5
|
+
#
|
|
6
|
+
# To request access to a new host, add it here and submit a PR.
|
|
7
|
+
# See doc/network-sandbox.md for details.
|
|
8
|
+
#
|
|
9
|
+
# This file is read from the git mirror (main branch), not from the
|
|
10
|
+
# conversation workspace. Changes take effect after merging to main.
|
|
11
|
+
#
|
|
12
|
+
# Pattern matching uses fnmatch-style wildcards (* and ?):
|
|
13
|
+
# - "*.github.com" matches "api.github.com" but NOT "github.com"
|
|
14
|
+
# - "/api/*" matches "/api/foo" but NOT "/api"
|
|
15
|
+
# - "/api" matches only "/api" exactly (no implicit prefix matching)
|
|
16
|
+
|
|
17
|
+
# Domains: all paths and methods allowed (exact match unless wildcards used)
|
|
18
|
+
domains:
|
|
19
|
+
- api.anthropic.com
|
|
20
|
+
|
|
21
|
+
# URL patterns: domain + path pattern required (fnmatch wildcards supported)
|
|
22
|
+
url_prefixes:
|
|
23
|
+
# Claude telemetry and error reporting (POST-only)
|
|
24
|
+
- host: statsig.anthropic.com
|
|
25
|
+
path: ""
|
|
26
|
+
methods: [POST]
|
|
27
|
+
- host: sentry.io
|
|
28
|
+
path: ""
|
|
29
|
+
methods: [POST]
|
|
30
|
+
|
|
31
|
+
# Python packages — read-only (uv sync in entrypoint)
|
|
32
|
+
- host: pypi.org
|
|
33
|
+
path: ""
|
|
34
|
+
methods: [GET, HEAD]
|
|
35
|
+
- host: files.pythonhosted.org
|
|
36
|
+
path: ""
|
|
37
|
+
methods: [GET, HEAD]
|
|
38
|
+
|
|
39
|
+
# GitHub — restricted to airut repositories
|
|
40
|
+
- host: github.com
|
|
41
|
+
path: /airutorg/airut*
|
|
42
|
+
methods: [GET, HEAD, POST]
|
|
43
|
+
- host: api.github.com
|
|
44
|
+
path: /repos/airutorg/airut*
|
|
45
|
+
- host: api.github.com
|
|
46
|
+
path: /graphql
|
|
47
|
+
methods: [POST]
|
|
48
|
+
- host: uploads.github.com
|
|
49
|
+
path: /repos/airutorg/airut*
|
|
50
|
+
methods: [POST]
|
|
51
|
+
- host: raw.githubusercontent.com
|
|
52
|
+
path: /airutorg/airut*
|
|
53
|
+
methods: [GET, HEAD]
|
|
54
|
+
- host: objects.githubusercontent.com
|
|
55
|
+
path: /airutorg/airut*
|
|
56
|
+
methods: [GET, HEAD]
|
|
57
|
+
- host: results-receiver.actions.githubusercontent.com
|
|
58
|
+
path: /rest/runs*
|
|
59
|
+
methods: [POST]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Code Quality
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
code-quality:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
16
|
+
- uses: astral-sh/setup-uv@v4
|
|
17
|
+
- run: uv python install 3.13
|
|
18
|
+
- run: uv sync
|
|
19
|
+
- name: Lint
|
|
20
|
+
run: uv run ruff check .
|
|
21
|
+
- name: Format check
|
|
22
|
+
run: uv run ruff format --check .
|
|
23
|
+
- name: Type check
|
|
24
|
+
run: uv run ty check .
|
|
25
|
+
- name: Markdown format check
|
|
26
|
+
run: uv run python scripts/check_markdown.py
|
|
27
|
+
- name: Test coverage
|
|
28
|
+
run: uv run pytest -n auto --cov=lib --cov-fail-under=100
|
|
29
|
+
- name: Worktree clean check
|
|
30
|
+
run: |
|
|
31
|
+
if [[ -n $(git status --porcelain) ]]; then
|
|
32
|
+
echo "ERROR: Uncommitted changes after formatting"
|
|
33
|
+
git status
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: E2E Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
e2e-email-gateway:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
timeout-minutes: 5
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
17
|
+
- uses: astral-sh/setup-uv@v4
|
|
18
|
+
- run: uv python install 3.13
|
|
19
|
+
- run: uv sync
|
|
20
|
+
|
|
21
|
+
- name: Run email gateway integration tests
|
|
22
|
+
run: |
|
|
23
|
+
uv run pytest tests/integration/ -n auto -v -x \
|
|
24
|
+
-W error::pytest.PytestUnraisableExceptionWarning \
|
|
25
|
+
-W error::RuntimeWarning \
|
|
26
|
+
--allow-hosts=127.0.0.1,localhost
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
with:
|
|
13
|
+
fetch-depth: 0 # Full history for git describe
|
|
14
|
+
- uses: astral-sh/setup-uv@v4
|
|
15
|
+
- run: uv python install 3.13
|
|
16
|
+
- run: uv build
|
|
17
|
+
- uses: actions/upload-artifact@v4
|
|
18
|
+
with:
|
|
19
|
+
name: dist
|
|
20
|
+
path: dist/
|
|
21
|
+
|
|
22
|
+
publish:
|
|
23
|
+
needs: build
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
environment: pypi
|
|
26
|
+
permissions:
|
|
27
|
+
id-token: write # Required for Trusted Publisher
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/download-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Security
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
security:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
16
|
+
- uses: astral-sh/setup-uv@v4
|
|
17
|
+
- run: uv python install 3.13
|
|
18
|
+
- run: uv sync
|
|
19
|
+
- name: License check
|
|
20
|
+
run: uv run pip-licenses
|
|
21
|
+
- name: Vulnerability scan
|
|
22
|
+
run: uv run uv-secure uv.lock
|
airut-0.8.0/.gitignore
ADDED