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.
Files changed (226) hide show
  1. airut-0.8.0/.airut/README.md +81 -0
  2. airut-0.8.0/.airut/airut.yaml +39 -0
  3. airut-0.8.0/.airut/container/Dockerfile +49 -0
  4. airut-0.8.0/.airut/container/gitconfig +9 -0
  5. airut-0.8.0/.airut/network-allowlist.yaml +59 -0
  6. airut-0.8.0/.claude/settings.json +4 -0
  7. airut-0.8.0/.github/workflows/code.yml +35 -0
  8. airut-0.8.0/.github/workflows/e2e.yml +26 -0
  9. airut-0.8.0/.github/workflows/publish.yml +33 -0
  10. airut-0.8.0/.github/workflows/security.yml +22 -0
  11. airut-0.8.0/.gitignore +11 -0
  12. airut-0.8.0/.mdformat.toml +4 -0
  13. airut-0.8.0/CLAUDE.md +347 -0
  14. airut-0.8.0/LICENSE +19 -0
  15. airut-0.8.0/PKG-INFO +261 -0
  16. airut-0.8.0/README.md +230 -0
  17. airut-0.8.0/SECURITY.md +21 -0
  18. airut-0.8.0/assets/logo-dark.svg +131 -0
  19. airut-0.8.0/assets/logo-square-transparent-bg.png +0 -0
  20. airut-0.8.0/assets/logo-square-white-bg.png +0 -0
  21. airut-0.8.0/assets/logo-transparent-bg.png +0 -0
  22. airut-0.8.0/assets/logo-white-bg.png +0 -0
  23. airut-0.8.0/assets/logo.svg +131 -0
  24. airut-0.8.0/config/airut.example.yaml +198 -0
  25. airut-0.8.0/doc/README.md +24 -0
  26. airut-0.8.0/doc/agentic-operation.md +302 -0
  27. airut-0.8.0/doc/architecture.md +328 -0
  28. airut-0.8.0/doc/deployment.md +635 -0
  29. airut-0.8.0/doc/execution-sandbox.md +129 -0
  30. airut-0.8.0/doc/gerrit-onboarding.md +218 -0
  31. airut-0.8.0/doc/m365-oauth2.md +227 -0
  32. airut-0.8.0/doc/network-sandbox.md +496 -0
  33. airut-0.8.0/doc/repo-onboarding.md +333 -0
  34. airut-0.8.0/doc/security.md +382 -0
  35. airut-0.8.0/lib/__init__.py +8 -0
  36. airut-0.8.0/lib/_bundled/__init__.py +12 -0
  37. airut-0.8.0/lib/_bundled/assets/__init__.py +6 -0
  38. airut-0.8.0/lib/_bundled/assets/logo.svg +131 -0
  39. airut-0.8.0/lib/_bundled/proxy/__init__.py +6 -0
  40. airut-0.8.0/lib/_bundled/proxy/aws_signing.py +1085 -0
  41. airut-0.8.0/lib/_bundled/proxy/dns_responder.py +230 -0
  42. airut-0.8.0/lib/_bundled/proxy/proxy-entrypoint.sh +69 -0
  43. airut-0.8.0/lib/_bundled/proxy/proxy.dockerfile +11 -0
  44. airut-0.8.0/lib/_bundled/proxy/proxy_filter.py +882 -0
  45. airut-0.8.0/lib/_version.py +5 -0
  46. airut-0.8.0/lib/airut.py +419 -0
  47. airut-0.8.0/lib/allowlist.py +126 -0
  48. airut-0.8.0/lib/claude_output/__init__.py +56 -0
  49. airut-0.8.0/lib/claude_output/extract.py +194 -0
  50. airut-0.8.0/lib/claude_output/parser.py +205 -0
  51. airut-0.8.0/lib/claude_output/types.py +124 -0
  52. airut-0.8.0/lib/conversation/__init__.py +38 -0
  53. airut-0.8.0/lib/conversation/conversation_layout.py +105 -0
  54. airut-0.8.0/lib/conversation/conversation_store.py +404 -0
  55. airut-0.8.0/lib/dashboard/__init__.py +51 -0
  56. airut-0.8.0/lib/dashboard/formatters.py +75 -0
  57. airut-0.8.0/lib/dashboard/handlers.py +908 -0
  58. airut-0.8.0/lib/dashboard/server.py +276 -0
  59. airut-0.8.0/lib/dashboard/sse.py +436 -0
  60. airut-0.8.0/lib/dashboard/tracker.py +448 -0
  61. airut-0.8.0/lib/dashboard/versioned.py +108 -0
  62. airut-0.8.0/lib/dashboard/views/__init__.py +70 -0
  63. airut-0.8.0/lib/dashboard/views/actions.py +765 -0
  64. airut-0.8.0/lib/dashboard/views/components.py +522 -0
  65. airut-0.8.0/lib/dashboard/views/dashboard.py +338 -0
  66. airut-0.8.0/lib/dashboard/views/network.py +231 -0
  67. airut-0.8.0/lib/dashboard/views/repo_detail.py +162 -0
  68. airut-0.8.0/lib/dashboard/views/styles.py +1038 -0
  69. airut-0.8.0/lib/dashboard/views/task_detail.py +309 -0
  70. airut-0.8.0/lib/dns.py +71 -0
  71. airut-0.8.0/lib/gateway/__init__.py +83 -0
  72. airut-0.8.0/lib/gateway/config.py +1555 -0
  73. airut-0.8.0/lib/gateway/conversation.py +257 -0
  74. airut-0.8.0/lib/gateway/dotenv_loader.py +72 -0
  75. airut-0.8.0/lib/gateway/listener.py +510 -0
  76. airut-0.8.0/lib/gateway/microsoft_oauth2.py +112 -0
  77. airut-0.8.0/lib/gateway/parsing.py +326 -0
  78. airut-0.8.0/lib/gateway/responder.py +255 -0
  79. airut-0.8.0/lib/gateway/security.py +390 -0
  80. airut-0.8.0/lib/gateway/service/__init__.py +48 -0
  81. airut-0.8.0/lib/gateway/service/email_replies.py +348 -0
  82. airut-0.8.0/lib/gateway/service/gateway.py +740 -0
  83. airut-0.8.0/lib/gateway/service/message_processing.py +732 -0
  84. airut-0.8.0/lib/gateway/service/repo_handler.py +330 -0
  85. airut-0.8.0/lib/gateway/service/usage_stats.py +105 -0
  86. airut-0.8.0/lib/gh/__init__.py +48 -0
  87. airut-0.8.0/lib/gh/ci.py +403 -0
  88. airut-0.8.0/lib/gh/pr.py +192 -0
  89. airut-0.8.0/lib/gh/review.py +350 -0
  90. airut-0.8.0/lib/git_mirror.py +495 -0
  91. airut-0.8.0/lib/git_version.py +211 -0
  92. airut-0.8.0/lib/html_to_text.py +545 -0
  93. airut-0.8.0/lib/install_services.py +251 -0
  94. airut-0.8.0/lib/logging.py +138 -0
  95. airut-0.8.0/lib/markdown.py +385 -0
  96. airut-0.8.0/lib/sandbox/__init__.py +71 -0
  97. airut-0.8.0/lib/sandbox/_entrypoint.py +41 -0
  98. airut-0.8.0/lib/sandbox/_image.py +221 -0
  99. airut-0.8.0/lib/sandbox/_network.py +85 -0
  100. airut-0.8.0/lib/sandbox/_output.py +139 -0
  101. airut-0.8.0/lib/sandbox/_proxy.py +728 -0
  102. airut-0.8.0/lib/sandbox/event_log.py +167 -0
  103. airut-0.8.0/lib/sandbox/network_log.py +84 -0
  104. airut-0.8.0/lib/sandbox/sandbox.py +220 -0
  105. airut-0.8.0/lib/sandbox/secrets.py +293 -0
  106. airut-0.8.0/lib/sandbox/task.py +434 -0
  107. airut-0.8.0/lib/sandbox/types.py +96 -0
  108. airut-0.8.0/pyproject.toml +114 -0
  109. airut-0.8.0/scripts/.gitkeep +0 -0
  110. airut-0.8.0/scripts/airut.py +23 -0
  111. airut-0.8.0/scripts/check_markdown.py +80 -0
  112. airut-0.8.0/scripts/ci.py +316 -0
  113. airut-0.8.0/scripts/hatch_build.py +113 -0
  114. airut-0.8.0/scripts/install_services.py +198 -0
  115. airut-0.8.0/scripts/pr.py +399 -0
  116. airut-0.8.0/spec/README.md +47 -0
  117. airut-0.8.0/spec/authentication.md +172 -0
  118. airut-0.8.0/spec/aws-sigv4-resigning.md +817 -0
  119. airut-0.8.0/spec/dashboard.md +247 -0
  120. airut-0.8.0/spec/gateway-architecture.md +501 -0
  121. airut-0.8.0/spec/image.md +146 -0
  122. airut-0.8.0/spec/integration-tests.md +46 -0
  123. airut-0.8.0/spec/live-dashboard.md +187 -0
  124. airut-0.8.0/spec/local-ci-runner.md +120 -0
  125. airut-0.8.0/spec/masked-secrets.md +246 -0
  126. airut-0.8.0/spec/multi-repo.md +316 -0
  127. airut-0.8.0/spec/network-sandbox.md +144 -0
  128. airut-0.8.0/spec/pr-workflow-tool.md +233 -0
  129. airut-0.8.0/spec/repo-config.md +121 -0
  130. airut-0.8.0/spec/sandbox.md +193 -0
  131. airut-0.8.0/tests/__init__.py +6 -0
  132. airut-0.8.0/tests/claude_output/__init__.py +4 -0
  133. airut-0.8.0/tests/claude_output/test_extract.py +329 -0
  134. airut-0.8.0/tests/claude_output/test_parser.py +370 -0
  135. airut-0.8.0/tests/claude_output/test_pipeline.py +815 -0
  136. airut-0.8.0/tests/claude_output/test_types.py +129 -0
  137. airut-0.8.0/tests/conftest.py +188 -0
  138. airut-0.8.0/tests/conversation/__init__.py +4 -0
  139. airut-0.8.0/tests/conversation/conftest.py +6 -0
  140. airut-0.8.0/tests/conversation/test_conversation_layout.py +98 -0
  141. airut-0.8.0/tests/conversation/test_conversation_store.py +542 -0
  142. airut-0.8.0/tests/dashboard/__init__.py +5 -0
  143. airut-0.8.0/tests/dashboard/conftest.py +173 -0
  144. airut-0.8.0/tests/dashboard/test_actions.py +1066 -0
  145. airut-0.8.0/tests/dashboard/test_formatters.py +95 -0
  146. airut-0.8.0/tests/dashboard/test_handlers.py +1701 -0
  147. airut-0.8.0/tests/dashboard/test_network.py +307 -0
  148. airut-0.8.0/tests/dashboard/test_server.py +1180 -0
  149. airut-0.8.0/tests/dashboard/test_sse.py +937 -0
  150. airut-0.8.0/tests/dashboard/test_tracker.py +611 -0
  151. airut-0.8.0/tests/dashboard/test_versioned.py +216 -0
  152. airut-0.8.0/tests/gateway/__init__.py +5 -0
  153. airut-0.8.0/tests/gateway/conftest.py +84 -0
  154. airut-0.8.0/tests/gateway/service/__init__.py +5 -0
  155. airut-0.8.0/tests/gateway/service/conftest.py +118 -0
  156. airut-0.8.0/tests/gateway/service/test_email_replies.py +369 -0
  157. airut-0.8.0/tests/gateway/service/test_gateway.py +1116 -0
  158. airut-0.8.0/tests/gateway/service/test_message_processing.py +1282 -0
  159. airut-0.8.0/tests/gateway/service/test_repo_handler.py +404 -0
  160. airut-0.8.0/tests/gateway/service/test_usage_stats.py +392 -0
  161. airut-0.8.0/tests/gateway/test_config.py +2463 -0
  162. airut-0.8.0/tests/gateway/test_conversation.py +326 -0
  163. airut-0.8.0/tests/gateway/test_dotenv_loader.py +141 -0
  164. airut-0.8.0/tests/gateway/test_integration.py +1660 -0
  165. airut-0.8.0/tests/gateway/test_listener.py +1249 -0
  166. airut-0.8.0/tests/gateway/test_microsoft_oauth2.py +150 -0
  167. airut-0.8.0/tests/gateway/test_parsing.py +678 -0
  168. airut-0.8.0/tests/gateway/test_responder.py +584 -0
  169. airut-0.8.0/tests/gateway/test_security.py +685 -0
  170. airut-0.8.0/tests/integration/__init__.py +5 -0
  171. airut-0.8.0/tests/integration/gateway/__init__.py +5 -0
  172. airut-0.8.0/tests/integration/gateway/conftest.py +280 -0
  173. airut-0.8.0/tests/integration/gateway/email_server.py +771 -0
  174. airut-0.8.0/tests/integration/gateway/environment.py +354 -0
  175. airut-0.8.0/tests/integration/gateway/mock_claude.py +248 -0
  176. airut-0.8.0/tests/integration/gateway/mock_podman.py +369 -0
  177. airut-0.8.0/tests/integration/gateway/mock_podman_wrapper.sh +4 -0
  178. airut-0.8.0/tests/integration/gateway/test_attachments.py +297 -0
  179. airut-0.8.0/tests/integration/gateway/test_authorization.py +255 -0
  180. airut-0.8.0/tests/integration/gateway/test_conversation_resume.py +289 -0
  181. airut-0.8.0/tests/integration/gateway/test_errors.py +204 -0
  182. airut-0.8.0/tests/integration/gateway/test_git_mirror_updates.py +571 -0
  183. airut-0.8.0/tests/integration/gateway/test_multi_repo.py +458 -0
  184. airut-0.8.0/tests/integration/gateway/test_multi_sender.py +336 -0
  185. airut-0.8.0/tests/integration/gateway/test_new_conversation.py +249 -0
  186. airut-0.8.0/tests/integration/gateway/test_outbox.py +497 -0
  187. airut-0.8.0/tests/integration/gateway/test_repo_init_failures.py +405 -0
  188. airut-0.8.0/tests/integration/gateway/test_streaming_and_stop.py +389 -0
  189. airut-0.8.0/tests/proxy/conftest.py +136 -0
  190. airut-0.8.0/tests/proxy/test_aws_signing.py +1388 -0
  191. airut-0.8.0/tests/proxy/test_dns_responder.py +500 -0
  192. airut-0.8.0/tests/proxy/test_proxy_allowlist.py +547 -0
  193. airut-0.8.0/tests/proxy/test_proxy_filter.py +2050 -0
  194. airut-0.8.0/tests/proxy/vectors.py +57 -0
  195. airut-0.8.0/tests/sandbox/__init__.py +0 -0
  196. airut-0.8.0/tests/sandbox/conftest.py +102 -0
  197. airut-0.8.0/tests/sandbox/test_entrypoint.py +60 -0
  198. airut-0.8.0/tests/sandbox/test_event_log.py +297 -0
  199. airut-0.8.0/tests/sandbox/test_image.py +370 -0
  200. airut-0.8.0/tests/sandbox/test_network.py +101 -0
  201. airut-0.8.0/tests/sandbox/test_network_log.py +180 -0
  202. airut-0.8.0/tests/sandbox/test_output.py +355 -0
  203. airut-0.8.0/tests/sandbox/test_proxy.py +1005 -0
  204. airut-0.8.0/tests/sandbox/test_sandbox.py +337 -0
  205. airut-0.8.0/tests/sandbox/test_secrets.py +462 -0
  206. airut-0.8.0/tests/sandbox/test_task.py +686 -0
  207. airut-0.8.0/tests/test_airut.py +509 -0
  208. airut-0.8.0/tests/test_allowlist.py +262 -0
  209. airut-0.8.0/tests/test_ci.py +401 -0
  210. airut-0.8.0/tests/test_dns.py +91 -0
  211. airut-0.8.0/tests/test_git_mirror.py +775 -0
  212. airut-0.8.0/tests/test_hatch_build.py +171 -0
  213. airut-0.8.0/tests/test_html_to_text.py +1013 -0
  214. airut-0.8.0/tests/test_install_services.py +432 -0
  215. airut-0.8.0/tests/test_lib/__init__.py +6 -0
  216. airut-0.8.0/tests/test_lib/test_gh/__init__.py +6 -0
  217. airut-0.8.0/tests/test_lib/test_gh/test_ci.py +623 -0
  218. airut-0.8.0/tests/test_lib/test_gh/test_pr.py +297 -0
  219. airut-0.8.0/tests/test_lib/test_gh/test_review.py +673 -0
  220. airut-0.8.0/tests/test_lib/test_git_version.py +352 -0
  221. airut-0.8.0/tests/test_lib/test_logging.py +206 -0
  222. airut-0.8.0/tests/test_markdown.py +579 -0
  223. airut-0.8.0/tests/test_scripts/__init__.py +5 -0
  224. airut-0.8.0/tests/test_scripts/test_pr.py +775 -0
  225. airut-0.8.0/uv.lock +974 -0
  226. 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,9 @@
1
+ [credential "https://github.com"]
2
+ helper =
3
+ helper = !/usr/bin/gh auth git-credential
4
+ [credential "https://gist.github.com"]
5
+ helper =
6
+ helper = !/usr/bin/gh auth git-credential
7
+ [user]
8
+ name = Airut
9
+ email = airut@airut.org
@@ -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,4 @@
1
+ {
2
+ "attribution": {"commit": "", "pr": ""},
3
+ "includeCoAuthoredBy": false
4
+ }
@@ -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
@@ -0,0 +1,11 @@
1
+ .DS_Store
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+ .coverage
7
+ .env
8
+ .update.lock
9
+ .gitconfig
10
+ lib/_version.py
11
+ /config/airut.yaml
@@ -0,0 +1,4 @@
1
+ wrap = 80
2
+ number = true
3
+
4
+ exclude = [".venv/**", ".pytest_cache/**"]