claudecord 0.2.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.
Files changed (214) hide show
  1. claudecord-0.2.1/.dockerignore +7 -0
  2. claudecord-0.2.1/.env.example +22 -0
  3. claudecord-0.2.1/.github/CODEOWNERS +1 -0
  4. claudecord-0.2.1/.github/ISSUE_TEMPLATE/bug_report.yml +41 -0
  5. claudecord-0.2.1/.github/ISSUE_TEMPLATE/config.yml +5 -0
  6. claudecord-0.2.1/.github/ISSUE_TEMPLATE/feature_request.yml +19 -0
  7. claudecord-0.2.1/.github/dependabot.yml +10 -0
  8. claudecord-0.2.1/.github/pull_request_template.md +23 -0
  9. claudecord-0.2.1/.github/workflows/deploy.yml +45 -0
  10. claudecord-0.2.1/.github/workflows/docker.yml +26 -0
  11. claudecord-0.2.1/.github/workflows/load.yml +25 -0
  12. claudecord-0.2.1/.github/workflows/pages.yml +28 -0
  13. claudecord-0.2.1/.github/workflows/pr-tests.yml +36 -0
  14. claudecord-0.2.1/.github/workflows/race.yml +47 -0
  15. claudecord-0.2.1/.github/workflows/release.yml +172 -0
  16. claudecord-0.2.1/.github/workflows/rust.yml +84 -0
  17. claudecord-0.2.1/.github/workflows/uptime.yml +23 -0
  18. claudecord-0.2.1/.github/workflows/web.yml +27 -0
  19. claudecord-0.2.1/.gitignore +19 -0
  20. claudecord-0.2.1/CODEMAP.md +219 -0
  21. claudecord-0.2.1/CONTRIBUTING.md +40 -0
  22. claudecord-0.2.1/Cargo.lock +2855 -0
  23. claudecord-0.2.1/Cargo.toml +38 -0
  24. claudecord-0.2.1/Dockerfile +36 -0
  25. claudecord-0.2.1/PKG-INFO +271 -0
  26. claudecord-0.2.1/README.md +256 -0
  27. claudecord-0.2.1/SECURITY.md +20 -0
  28. claudecord-0.2.1/deploy/baremetal/Caddyfile +6 -0
  29. claudecord-0.2.1/deploy/baremetal/bootstrap.sh +127 -0
  30. claudecord-0.2.1/deploy/baremetal/claudecord-hub.service +31 -0
  31. claudecord-0.2.1/deploy/compose.yml +43 -0
  32. claudecord-0.2.1/dist/claudecord-0.2.1-py3-none-macosx_11_0_arm64.whl +0 -0
  33. claudecord-0.2.1/examples/briefs.rs +30 -0
  34. claudecord-0.2.1/examples/replay.rs +251 -0
  35. claudecord-0.2.1/init.md +77 -0
  36. claudecord-0.2.1/npm/claudecord/bin/claudecord.js +40 -0
  37. claudecord-0.2.1/npm/claudecord/package.json +11 -0
  38. claudecord-0.2.1/pyproject.toml +27 -0
  39. claudecord-0.2.1/scripts/check.sh +37 -0
  40. claudecord-0.2.1/scripts/codemap.py +56 -0
  41. claudecord-0.2.1/scripts/cost/baseline.json +49 -0
  42. claudecord-0.2.1/scripts/cost/benchmark.py +82 -0
  43. claudecord-0.2.1/scripts/cost/coalescing.py +37 -0
  44. claudecord-0.2.1/scripts/cost/overhead.py +11 -0
  45. claudecord-0.2.1/scripts/cost/team_benchmark.py +87 -0
  46. claudecord-0.2.1/scripts/load/hub.js +89 -0
  47. claudecord-0.2.1/scripts/load/profile.sh +32 -0
  48. claudecord-0.2.1/scripts/load/smoke.sh +13 -0
  49. claudecord-0.2.1/scripts/package_npm.py +63 -0
  50. claudecord-0.2.1/scripts/publish_npm.sh +12 -0
  51. claudecord-0.2.1/scripts/release.sh +28 -0
  52. claudecord-0.2.1/scripts/smoke.sh +41 -0
  53. claudecord-0.2.1/scripts/test_packaging.py +105 -0
  54. claudecord-0.2.1/scripts/tests_gate.py +53 -0
  55. claudecord-0.2.1/scripts/tsan_filter.py +52 -0
  56. claudecord-0.2.1/site/index.html +40 -0
  57. claudecord-0.2.1/src/agents/adapters.rs +322 -0
  58. claudecord-0.2.1/src/agents/mod.rs +5 -0
  59. claudecord-0.2.1/src/agents/text.rs +126 -0
  60. claudecord-0.2.1/src/cli/discord.rs +220 -0
  61. claudecord-0.2.1/src/cli/export.rs +55 -0
  62. claudecord-0.2.1/src/cli/hub.rs +428 -0
  63. claudecord-0.2.1/src/cli/machine.rs +651 -0
  64. claudecord-0.2.1/src/cli/mod.rs +189 -0
  65. claudecord-0.2.1/src/cli/storage.rs +209 -0
  66. claudecord-0.2.1/src/cli/uptime.rs +135 -0
  67. claudecord-0.2.1/src/cli/verbs.rs +114 -0
  68. claudecord-0.2.1/src/control/mod.rs +528 -0
  69. claudecord-0.2.1/src/control/registry.rs +258 -0
  70. claudecord-0.2.1/src/control/seal.rs +252 -0
  71. claudecord-0.2.1/src/device/config.rs +56 -0
  72. claudecord-0.2.1/src/device/daemon.rs +1493 -0
  73. claudecord-0.2.1/src/device/doctor.rs +105 -0
  74. claudecord-0.2.1/src/device/enroll.rs +157 -0
  75. claudecord-0.2.1/src/device/inject.rs +120 -0
  76. claudecord-0.2.1/src/device/ipc.rs +209 -0
  77. claudecord-0.2.1/src/device/link.rs +439 -0
  78. claudecord-0.2.1/src/device/logs.rs +179 -0
  79. claudecord-0.2.1/src/device/mod.rs +27 -0
  80. claudecord-0.2.1/src/device/pty.rs +173 -0
  81. claudecord-0.2.1/src/device/terminal.rs +194 -0
  82. claudecord-0.2.1/src/device/tmux.rs +361 -0
  83. claudecord-0.2.1/src/discord/api.rs +371 -0
  84. claudecord-0.2.1/src/discord/bridge.rs +862 -0
  85. claudecord-0.2.1/src/discord/commands.rs +363 -0
  86. claudecord-0.2.1/src/discord/fake.rs +340 -0
  87. claudecord-0.2.1/src/discord/gateway.rs +187 -0
  88. claudecord-0.2.1/src/discord/mod.rs +14 -0
  89. claudecord-0.2.1/src/discord/perms.rs +65 -0
  90. claudecord-0.2.1/src/export/mod.rs +64 -0
  91. claudecord-0.2.1/src/export/obsidian.rs +571 -0
  92. claudecord-0.2.1/src/health/mod.rs +97 -0
  93. claudecord-0.2.1/src/health/probes_core.rs +879 -0
  94. claudecord-0.2.1/src/health/probes_hosted.rs +167 -0
  95. claudecord-0.2.1/src/health/probes_live.rs +1239 -0
  96. claudecord-0.2.1/src/hub/access.rs +74 -0
  97. claudecord-0.2.1/src/hub/asks.rs +213 -0
  98. claudecord-0.2.1/src/hub/briefs.rs +35 -0
  99. claudecord-0.2.1/src/hub/controls.rs +242 -0
  100. claudecord-0.2.1/src/hub/core.rs +924 -0
  101. claudecord-0.2.1/src/hub/effects.rs +148 -0
  102. claudecord-0.2.1/src/hub/expiry.rs +109 -0
  103. claudecord-0.2.1/src/hub/files.rs +217 -0
  104. claudecord-0.2.1/src/hub/handoff.rs +368 -0
  105. claudecord-0.2.1/src/hub/mod.rs +42 -0
  106. claudecord-0.2.1/src/hub/model.rs +275 -0
  107. claudecord-0.2.1/src/hub/permissions.rs +356 -0
  108. claudecord-0.2.1/src/hub/routing.rs +482 -0
  109. claudecord-0.2.1/src/hub/snapshot.rs +346 -0
  110. claudecord-0.2.1/src/hub/tasks.rs +291 -0
  111. claudecord-0.2.1/src/hub/tracked.rs +230 -0
  112. claudecord-0.2.1/src/lib.rs +55 -0
  113. claudecord-0.2.1/src/log.rs +162 -0
  114. claudecord-0.2.1/src/main.rs +14 -0
  115. claudecord-0.2.1/src/metrics/events.rs +345 -0
  116. claudecord-0.2.1/src/metrics/mod.rs +301 -0
  117. claudecord-0.2.1/src/notify.rs +48 -0
  118. claudecord-0.2.1/src/protocol/mod.rs +571 -0
  119. claudecord-0.2.1/src/security/codes.rs +16 -0
  120. claudecord-0.2.1/src/security/env.rs +67 -0
  121. claudecord-0.2.1/src/security/limits.rs +82 -0
  122. claudecord-0.2.1/src/security/mod.rs +6 -0
  123. claudecord-0.2.1/src/security/redact.rs +165 -0
  124. claudecord-0.2.1/src/server/actor.rs +392 -0
  125. claudecord-0.2.1/src/server/app.rs +42 -0
  126. claudecord-0.2.1/src/server/bots.rs +473 -0
  127. claudecord-0.2.1/src/server/demo.rs +232 -0
  128. claudecord-0.2.1/src/server/disk.rs +175 -0
  129. claudecord-0.2.1/src/server/gateway.rs +788 -0
  130. claudecord-0.2.1/src/server/health.rs +303 -0
  131. claudecord-0.2.1/src/server/login.rs +304 -0
  132. claudecord-0.2.1/src/server/mod.rs +571 -0
  133. claudecord-0.2.1/src/server/session.rs +137 -0
  134. claudecord-0.2.1/src/server/web/app.css +60 -0
  135. claudecord-0.2.1/src/server/web/app.js +193 -0
  136. claudecord-0.2.1/src/server/web/index.html +61 -0
  137. claudecord-0.2.1/src/server/web.rs +249 -0
  138. claudecord-0.2.1/src/store/bucket.rs +232 -0
  139. claudecord-0.2.1/src/store/mod.rs +812 -0
  140. claudecord-0.2.1/src/store/uptime.rs +58 -0
  141. claudecord-0.2.1/src/sync.rs +17 -0
  142. claudecord-0.2.1/src/task.rs +110 -0
  143. claudecord-0.2.1/src/uptime/mod.rs +275 -0
  144. claudecord-0.2.1/testdata/conformance/adapter_argv.json +158 -0
  145. claudecord-0.2.1/testdata/conformance/adapter_detect.json +525 -0
  146. claudecord-0.2.1/testdata/conformance/agent_spec.json +198 -0
  147. claudecord-0.2.1/testdata/conformance/app_id.json +34 -0
  148. claudecord-0.2.1/testdata/conformance/bucket.json +62 -0
  149. claudecord-0.2.1/testdata/conformance/detect_limit.json +80 -0
  150. claudecord-0.2.1/testdata/conformance/env_scrub.json +66 -0
  151. claudecord-0.2.1/testdata/conformance/failure_limiter.json +56 -0
  152. claudecord-0.2.1/testdata/conformance/file_secrets.json +72 -0
  153. claudecord-0.2.1/testdata/conformance/format_deliveries.json +39 -0
  154. claudecord-0.2.1/testdata/conformance/hub_frame_spawn_strips_cwd.json +21 -0
  155. claudecord-0.2.1/testdata/conformance/hub_frames.json +130 -0
  156. claudecord-0.2.1/testdata/conformance/junk_frames.json +32 -0
  157. claudecord-0.2.1/testdata/conformance/metrics.json +227 -0
  158. claudecord-0.2.1/testdata/conformance/node_frames.json +638 -0
  159. claudecord-0.2.1/testdata/conformance/pair_code_format.json +27 -0
  160. claudecord-0.2.1/testdata/conformance/parse_menu.json +99 -0
  161. claudecord-0.2.1/testdata/conformance/permissions.json +3 -0
  162. claudecord-0.2.1/testdata/conformance/project_slug.json +46 -0
  163. claudecord-0.2.1/testdata/conformance/quote_body.json +22 -0
  164. claudecord-0.2.1/testdata/conformance/redact.json +199 -0
  165. claudecord-0.2.1/testdata/conformance/safe_name.json +36 -0
  166. claudecord-0.2.1/testdata/conformance/sensitive_path.json +114 -0
  167. claudecord-0.2.1/testdata/conformance/slug.json +74 -0
  168. claudecord-0.2.1/testdata/conformance/startup_choice.json +32 -0
  169. claudecord-0.2.1/testdata/conformance/strip_control.json +54 -0
  170. claudecord-0.2.1/tests/it/bucket.rs +405 -0
  171. claudecord-0.2.1/tests/it/conformance.rs +400 -0
  172. claudecord-0.2.1/tests/it/device_link.rs +344 -0
  173. claudecord-0.2.1/tests/it/device_pty.rs +190 -0
  174. claudecord-0.2.1/tests/it/device_tmux.rs +106 -0
  175. claudecord-0.2.1/tests/it/discord_bridge.rs +823 -0
  176. claudecord-0.2.1/tests/it/durability.rs +289 -0
  177. claudecord-0.2.1/tests/it/e2e.rs +1250 -0
  178. claudecord-0.2.1/tests/it/edge.rs +344 -0
  179. claudecord-0.2.1/tests/it/export.rs +573 -0
  180. claudecord-0.2.1/tests/it/gateway.rs +881 -0
  181. claudecord-0.2.1/tests/it/health.rs +60 -0
  182. claudecord-0.2.1/tests/it/hub.rs +2020 -0
  183. claudecord-0.2.1/tests/it/logs.rs +79 -0
  184. claudecord-0.2.1/tests/it/main.rs +27 -0
  185. claudecord-0.2.1/tests/it/races.rs +311 -0
  186. claudecord-0.2.1/tests/it/resilience.rs +359 -0
  187. claudecord-0.2.1/tests/it/robust.rs +484 -0
  188. claudecord-0.2.1/tests/it/server.rs +1145 -0
  189. claudecord-0.2.1/tests/it/store.rs +244 -0
  190. claudecord-0.2.1/tests/it/tiering.rs +227 -0
  191. claudecord-0.2.1/tests/it/token_budget.rs +285 -0
  192. claudecord-0.2.1/tests/it/uptime.rs +342 -0
  193. claudecord-0.2.1/tests/it/web_login.rs +528 -0
  194. claudecord-0.2.1/web/.gitignore +1 -0
  195. claudecord-0.2.1/web/dist/app.css +2 -0
  196. claudecord-0.2.1/web/dist/app.js +11 -0
  197. claudecord-0.2.1/web/dist/index.html +14 -0
  198. claudecord-0.2.1/web/index.html +13 -0
  199. claudecord-0.2.1/web/package-lock.json +1403 -0
  200. claudecord-0.2.1/web/package.json +25 -0
  201. claudecord-0.2.1/web/src/App.tsx +68 -0
  202. claudecord-0.2.1/web/src/api.ts +93 -0
  203. claudecord-0.2.1/web/src/hooks.ts +54 -0
  204. claudecord-0.2.1/web/src/main.tsx +13 -0
  205. claudecord-0.2.1/web/src/pages/Activate.tsx +93 -0
  206. claudecord-0.2.1/web/src/pages/Bots.tsx +46 -0
  207. claudecord-0.2.1/web/src/pages/Insights.tsx +250 -0
  208. claudecord-0.2.1/web/src/pages/Login.tsx +21 -0
  209. claudecord-0.2.1/web/src/pages/Machines.tsx +37 -0
  210. claudecord-0.2.1/web/src/pages/Overview.tsx +149 -0
  211. claudecord-0.2.1/web/src/pages/Setup.tsx +291 -0
  212. claudecord-0.2.1/web/src/styles.css +63 -0
  213. claudecord-0.2.1/web/tsconfig.json +17 -0
  214. claudecord-0.2.1/web/vite.config.ts +24 -0
@@ -0,0 +1,7 @@
1
+ # Nothing the image is not built from goes to the builder (it is sent on every build).
2
+ **
3
+ !Cargo.toml
4
+ !Cargo.lock
5
+ !src/**
6
+ !examples/**
7
+ !web/dist/**
@@ -0,0 +1,22 @@
1
+ # Optional settings. Copy to .env if you like; claudecord does not read .env itself, so export these in your shell or put them in
2
+ # the systemd unit. NONE of these are secrets: the Discord token, the OAuth secret and bucket keys are never environment
3
+ # variables. They live in files in the hub's data folder (mode 0600), written by `claudecord discord set` and
4
+ # `claudecord storage ...`.
5
+
6
+ # How much is logged: error, warn, info (default) or debug.
7
+ # CLAUDECORD_LOG=info
8
+
9
+ # Where a machine keeps its login, daemon socket and per-agent logs (default: ~/.claudecord).
10
+ # CLAUDECORD_HOME=
11
+
12
+ # Machine placement: how many agents this machine should run at once, and labels such as gpu.
13
+ # CLAUDECORD_MAX_AGENTS=8
14
+ # CLAUDECORD_LABELS=gpu,fast
15
+
16
+ # How agents run: tmux (default where installed) or pty (the built-in terminal, used on native Windows).
17
+ # CLAUDECORD_TERMINAL=tmux
18
+ # The name of the private tmux server claudecord uses, so it never touches your own tmux.
19
+ # CLAUDECORD_TMUX_SOCKET=claudecord
20
+
21
+ # npm package only: run this program instead of the platform package's (for development).
22
+ # CLAUDECORD_BINARY=
@@ -0,0 +1 @@
1
+ * @kanavdhanda
@@ -0,0 +1,41 @@
1
+ name: Bug report
2
+ description: Something does not work
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened
9
+ description: And what you expected instead.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: repro
14
+ attributes:
15
+ label: Steps to reproduce
16
+ placeholder: "1. Run ...\n2. Then ...\n3. See ..."
17
+ validations:
18
+ required: true
19
+ - type: input
20
+ id: version
21
+ attributes:
22
+ label: Version
23
+ description: Output of `npx claudecord --version`, or the commit.
24
+ - type: dropdown
25
+ id: os
26
+ attributes:
27
+ label: Operating system
28
+ options: [macOS, Linux, Windows (WSL2), Windows (native)]
29
+ validations:
30
+ required: true
31
+ - type: dropdown
32
+ id: agent
33
+ attributes:
34
+ label: Agent
35
+ options: [Claude Code, agy, Codex, Not agent specific]
36
+ - type: textarea
37
+ id: logs
38
+ attributes:
39
+ label: Logs
40
+ description: "Remove anything secret first. The daemon log is in ~/.claude-mesh/daemon.log."
41
+ render: shell
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Report a security problem
4
+ url: https://github.com/kanavdhanda/claudeCord/security/advisories/new
5
+ about: Please report security problems privately, not in a public issue.
@@ -0,0 +1,19 @@
1
+ name: Feature request
2
+ description: Suggest an improvement
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What are you trying to do
9
+ description: The problem first, before the solution.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: idea
14
+ attributes:
15
+ label: What would you like to happen
16
+ - type: textarea
17
+ id: alternatives
18
+ attributes:
19
+ label: What have you tried or considered
@@ -0,0 +1,10 @@
1
+ # Opens pull requests for new versions of the Rust crates and the GitHub actions. Each one runs the full CI, so an update
2
+ # only merges when every test, the self-check and the smoke test still pass.
3
+ version: 2
4
+ updates:
5
+ - package-ecosystem: cargo
6
+ directory: /
7
+ schedule: { interval: weekly }
8
+ - package-ecosystem: github-actions
9
+ directory: /
10
+ schedule: { interval: weekly }
@@ -0,0 +1,23 @@
1
+ ## What and why
2
+
3
+ <!-- One or two sentences. -->
4
+
5
+ ## Tests
6
+
7
+ <!-- Which test fails without this change? (The test bot comments if none was added.) -->
8
+
9
+ ## Cost impact
10
+
11
+ <!-- Does this change how many agent turns or tokens a message costs? Write "none" if it does not touch delivery, briefs or
12
+ anything an agent reads, and say why. If it does, say what changes (more turns? a longer standing brief?) and show the
13
+ measurement (scripts/cost/ has the benchmarks; they spend real tokens, so run them yourself, CI does not). -->
14
+
15
+ Cost impact:
16
+
17
+ ## Scale impact
18
+
19
+ <!-- Does this change how many users, machines or agents one hub can carry? Write "none" if not, and say why. If it adds work
20
+ per connection, per message or per agent (memory held, database writes, timers), say how much, and run
21
+ scripts/load/smoke.sh (or the load.yml workflow) to show the numbers. -->
22
+
23
+ Scale impact:
@@ -0,0 +1,45 @@
1
+ name: deploy
2
+
3
+ # Updates the hub on your server and checks it is ready. The server builds the image itself from a clone of this repository (nothing is
4
+ # published to a registry). If the new version does not become ready, the one that was running is put back. Run it by hand
5
+ # (Actions > deploy > Run workflow) with a git ref: a release tag such as v0.2.1, a branch, or a commit.
6
+ # Set up once: secrets DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY, DEPLOY_KNOWN_HOSTS; variables HUB_URL (https://claudecord.kdhanda.com) and
7
+ # DEPLOY_DIR (the clone of the repository on the server, such as /opt/claudeCord), with deploy/compose.yml already set up there.
8
+ on:
9
+ workflow_dispatch:
10
+ inputs:
11
+ ref:
12
+ description: Git ref to run (v0.2.1, main, or a commit)
13
+ required: true
14
+ default: main
15
+
16
+ concurrency: deploy
17
+
18
+ jobs:
19
+ deploy:
20
+ if: vars.DEPLOY_DIR != ''
21
+ runs-on: ubuntu-latest
22
+ environment: production
23
+ steps:
24
+ - name: Trust only the server we pinned
25
+ run: |
26
+ mkdir -p ~/.ssh
27
+ echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id; chmod 600 ~/.ssh/id
28
+ echo "${{ secrets.DEPLOY_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
29
+ - name: Build on the server, restart, check, roll back on failure
30
+ env:
31
+ REF: ${{ inputs.ref }}
32
+ DIR: ${{ vars.DEPLOY_DIR }}
33
+ run: |
34
+ ssh_ () { ssh -i ~/.ssh/id -o StrictHostKeyChecking=yes "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" "$@"; }
35
+ # The running image is kept as claudecord:previous, and the commit it came from in .previous, so a failed deploy can put it back.
36
+ ssh_ "set -e; cd '$DIR' && git rev-parse HEAD > .previous && { docker tag claudecord:local claudecord:previous 2>/dev/null || true; } \
37
+ && git fetch --tags origin && git checkout --detach '$REF' && cd deploy && docker compose up -d --build"
38
+ for i in $(seq 1 20); do
39
+ code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "${{ vars.HUB_URL }}/readyz" || true)
40
+ [ "$code" = "200" ] && { echo "ready after $i checks"; exit 0; }
41
+ sleep 3
42
+ done
43
+ echo "the new version never became ready, putting the previous one back"
44
+ ssh_ "set -e; cd '$DIR' && git checkout --detach \$(cat .previous) && docker tag claudecord:previous claudecord:local && cd deploy && docker compose up -d --no-build"
45
+ exit 1
@@ -0,0 +1,26 @@
1
+ name: docker
2
+
3
+ # Checks that the hub's container image still builds. Nothing is published anywhere: the image is built on the server itself
4
+ # (deploy/compose.yml), so a change to the Dockerfile or to what goes into it is caught here and not on the server.
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+ paths: [Dockerfile, .dockerignore, Cargo.toml, Cargo.lock, "src/**", "web/dist/**", "examples/**", .github/workflows/docker.yml]
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ image:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: docker/setup-buildx-action@v3
21
+ - uses: docker/build-push-action@v6
22
+ with:
23
+ context: .
24
+ push: false
25
+ cache-from: type=gha
26
+ cache-to: type=gha,mode=max
@@ -0,0 +1,25 @@
1
+ name: load
2
+
3
+ # Runs the k6 load test (users with three bots each, all talking at once) against a hub limited to the size of a free
4
+ # server. Run it by hand from the Actions tab; it is never run on a push because it takes a long time.
5
+ on:
6
+ workflow_dispatch:
7
+ inputs:
8
+ users:
9
+ description: Simulated users (each has three bots)
10
+ default: "10000"
11
+
12
+ jobs:
13
+ load:
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ profile: [oracle-arm, small-vps, oracle-micro]
18
+ runs-on: ubuntu-latest
19
+ # Long and heavy, so each size waits for your approval (Settings > Environments > load-test > Required reviewers).
20
+ environment: load-test
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: grafana/setup-k6-action@v1
24
+ - run: docker build -t claudecord .
25
+ - run: scripts/load/profile.sh ${{ matrix.profile }} ${{ inputs.users }} 60
@@ -0,0 +1,28 @@
1
+ name: pages
2
+
3
+ # Publishes the one-page site in site/ to GitHub Pages (free). Turn it on once: Settings > Pages > Source: GitHub Actions.
4
+ # The site is plain HTML with no scripts, no cookies and no analytics.
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ paths: ["site/**"]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ pages: write
13
+ id-token: write
14
+ contents: read
15
+
16
+ jobs:
17
+ deploy:
18
+ runs-on: ubuntu-latest
19
+ environment:
20
+ name: github-pages
21
+ url: ${{ steps.deployment.outputs.page_url }}
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: actions/upload-pages-artifact@v3
25
+ with:
26
+ path: site
27
+ - id: deployment
28
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,36 @@
1
+ name: pr-tests
2
+
3
+ # A bot that reads each pull request and comments when source changed without a test. It keeps one comment up to date
4
+ # instead of adding a new one each push, and fails the check so it is not missed.
5
+ on:
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ tests-came-with-the-change:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
19
+ - run: python3 scripts/tests_gate.py --self-check
20
+ - id: gate
21
+ run: |
22
+ set +e
23
+ python3 scripts/tests_gate.py "origin/${{ github.base_ref }}" > verdict.md
24
+ echo "status=$?" >> "$GITHUB_OUTPUT"
25
+ - uses: actions/github-script@v7
26
+ with:
27
+ script: |
28
+ const body = '<!-- tests-gate -->\n' + require('fs').readFileSync('verdict.md', 'utf8');
29
+ const { owner, repo } = context.repo;
30
+ const issue_number = context.issue.number;
31
+ const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number });
32
+ const mine = comments.find((c) => c.body.startsWith('<!-- tests-gate -->'));
33
+ if (mine) await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body });
34
+ else await github.rest.issues.createComment({ owner, repo, issue_number, body });
35
+ - if: steps.gate.outputs.status != '0'
36
+ run: exit 1
@@ -0,0 +1,47 @@
1
+ name: race
2
+
3
+ # Looks for timing bugs: things that pass most of the time and fail when two actions land together.
4
+ # stress runs every test many times, with very few and very many threads, so order-dependent tests show themselves.
5
+ # sanitizer runs the concurrency tests under ThreadSanitizer, which reports data races in the program and its dependencies.
6
+ # The stress run happens every night; the sanitizer is slow, so it waits for your approval
7
+ # (Settings > Environments > sanitizers > Required reviewers) and is started by hand.
8
+ on:
9
+ schedule:
10
+ - cron: "17 3 * * *"
11
+ workflow_dispatch:
12
+
13
+ jobs:
14
+ stress:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: dtolnay/rust-toolchain@stable
19
+ - uses: Swatinem/rust-cache@v2
20
+ - run: cargo test --no-run --release
21
+ - run: |
22
+ for i in 1 2 3; do
23
+ cargo test --release -- --test-threads=1 || exit 1
24
+ cargo test --release -- --test-threads=32 || exit 1
25
+ done
26
+ - run: 'for i in $(seq 50); do cargo test --release --test it races:: -q || exit 1; done'
27
+
28
+ sanitizer:
29
+ if: github.event_name == 'workflow_dispatch'
30
+ runs-on: ubuntu-latest
31
+ environment: sanitizers
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: dtolnay/rust-toolchain@nightly
35
+ with:
36
+ components: rust-src
37
+ # Only the concurrency tests that do not depend on timing run here: under the sanitizer everything is 10 to 20 times slower, which makes
38
+ # timeouts in the socket tests fail for no reason that matters.
39
+ # ThreadSanitizer cannot see through tokio and the bundled SQLite, so it reports races inside them. The filter fails the job only if a
40
+ # racing access is in claudeCord's own code, or a test fails (see scripts/tsan_filter.py).
41
+ - run: |
42
+ set +e
43
+ cargo test -Zbuild-std --target x86_64-unknown-linux-gnu --test it -- races:: durability:: edge:: tiering:: hub:: 2>&1 | tee tsan.log
44
+ python3 scripts/tsan_filter.py tsan.log
45
+ env:
46
+ RUSTFLAGS: -Zsanitizer=thread
47
+ RUSTDOCFLAGS: -Zsanitizer=thread
@@ -0,0 +1,172 @@
1
+ name: release
2
+
3
+ # Push a tag such as v0.2.0 to release. One tag builds the program for every platform and makes, from those same builds:
4
+ # - files on a draft GitHub release,
5
+ # - pip wheels (PyPI),
6
+ # - npm packages (one per platform, plus the main `claudecord` package).
7
+ # Publishing to PyPI and npm only happens when it is set up: PyPI uses trusted publishing (no secret to keep), npm uses the NPM_TOKEN
8
+ # repository secret (a granular token with read and write on all packages and "bypass two-factor" on, because no one is there to answer a passkey).
9
+ # Ship with scripts/release.sh X.Y.Z, which bumps the version, tags it and pushes; this file does the rest. Without them the release still builds and the draft release holds every file.
10
+ on:
11
+ push:
12
+ tags: ["v*"]
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: write
17
+
18
+ jobs:
19
+ version:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ # A tag that does not match Cargo.toml would publish packages with the wrong number, so stop here.
24
+ - run: |
25
+ v=$(sed -n 's/^version *= *"\(.*\)"/\1/p' Cargo.toml | head -1)
26
+ if [ "${GITHUB_REF_TYPE}" = "tag" ] && [ "${GITHUB_REF_NAME}" != "v$v" ]; then
27
+ echo "tag ${GITHUB_REF_NAME} does not match Cargo.toml version $v"; exit 1
28
+ fi
29
+
30
+ # Before anything is published, the packages are installed into clean folders and run on every platform.
31
+ verify-packaging:
32
+ needs: version
33
+ strategy:
34
+ fail-fast: false
35
+ matrix:
36
+ os: [ubuntu-latest, macos-latest, windows-latest]
37
+ runs-on: ${{ matrix.os }}
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ - uses: dtolnay/rust-toolchain@stable
41
+ - uses: Swatinem/rust-cache@v2
42
+ - uses: astral-sh/setup-uv@v7
43
+ - uses: actions/setup-node@v4
44
+ with:
45
+ node-version: 22
46
+ - run: python3 scripts/test_packaging.py
47
+
48
+ build:
49
+ needs: version
50
+ strategy:
51
+ fail-fast: false
52
+ matrix:
53
+ include:
54
+ - { os: ubuntu-latest, target: x86_64-unknown-linux-musl, name: linux-x64, ext: "" }
55
+ - { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-musl, name: linux-arm64, ext: "" }
56
+ - { os: macos-latest, target: aarch64-apple-darwin, name: macos-arm64, ext: "" }
57
+ - { os: macos-15-intel, target: x86_64-apple-darwin, name: macos-x64, ext: "" }
58
+ - { os: windows-latest, target: x86_64-pc-windows-msvc, name: windows-x64, ext: ".exe" }
59
+ runs-on: ${{ matrix.os }}
60
+ steps:
61
+ - uses: actions/checkout@v4
62
+ - uses: dtolnay/rust-toolchain@stable
63
+ with:
64
+ targets: ${{ matrix.target }}
65
+ - run: sudo apt-get update && sudo apt-get install -y musl-tools
66
+ if: contains(matrix.target, 'musl')
67
+ - run: cargo build --release --target ${{ matrix.target }} --bin claudecord
68
+ env:
69
+ # The address a downloaded program goes to when nothing else is said (src/device/enroll.rs).
70
+ CLAUDECORD_HUB: https://claudecord.kdhanda.com
71
+ - run: cp target/${{ matrix.target }}/release/claudecord${{ matrix.ext }} claudecord-${{ matrix.name }}${{ matrix.ext }}
72
+ shell: bash
73
+ - uses: actions/upload-artifact@v4
74
+ with:
75
+ name: bin-${{ matrix.name }}
76
+ path: claudecord-${{ matrix.name }}${{ matrix.ext }}
77
+
78
+ # Wheels for glibc Linux (almost every distribution) are built in maturin's manylinux container, where the C compiler the TLS library needs is
79
+ # present. The standalone programs further up are static musl builds, which is what npm and the release files use.
80
+ wheels:
81
+ needs: version
82
+ strategy:
83
+ fail-fast: false
84
+ matrix:
85
+ include:
86
+ - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, extra: "" }
87
+ - { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu, extra: "" }
88
+ - { os: macos-latest, target: aarch64-apple-darwin, extra: "" }
89
+ - { os: macos-15-intel, target: x86_64-apple-darwin, extra: "" }
90
+ - { os: windows-latest, target: x86_64-pc-windows-msvc, extra: "" }
91
+ runs-on: ${{ matrix.os }}
92
+ steps:
93
+ - uses: actions/checkout@v4
94
+ - uses: PyO3/maturin-action@v1
95
+ with:
96
+ target: ${{ matrix.target }}
97
+ args: --release --out dist ${{ matrix.extra }}
98
+ - uses: actions/upload-artifact@v4
99
+ with:
100
+ name: wheel-${{ matrix.target }}
101
+ path: dist
102
+
103
+ sdist:
104
+ needs: version
105
+ runs-on: ubuntu-latest
106
+ steps:
107
+ - uses: actions/checkout@v4
108
+ - uses: PyO3/maturin-action@v1
109
+ with:
110
+ command: sdist
111
+ args: --out dist
112
+ - uses: actions/upload-artifact@v4
113
+ with:
114
+ name: sdist
115
+ path: dist
116
+
117
+ publish-pypi:
118
+ needs: [build, wheels, sdist, verify-packaging]
119
+ if: startsWith(github.ref, 'refs/tags/v')
120
+ runs-on: ubuntu-latest
121
+ environment: pypi
122
+ permissions:
123
+ id-token: write
124
+ steps:
125
+ - uses: actions/download-artifact@v4
126
+ with:
127
+ pattern: "{wheel-*,sdist}"
128
+ path: dist
129
+ merge-multiple: true
130
+ # Trusted publishing: PyPI is told once that this repository and workflow may publish, so there is no password to leak.
131
+ - uses: pypa/gh-action-pypi-publish@release/v1
132
+ continue-on-error: true
133
+
134
+ publish-npm:
135
+ needs: [build, verify-packaging]
136
+ if: startsWith(github.ref, 'refs/tags/v')
137
+ runs-on: ubuntu-latest
138
+ environment: npm
139
+ steps:
140
+ - uses: actions/checkout@v4
141
+ - uses: actions/setup-node@v4
142
+ with:
143
+ node-version: 22
144
+ registry-url: https://registry.npmjs.org
145
+ - uses: actions/download-artifact@v4
146
+ with:
147
+ pattern: "bin-*"
148
+ path: bins
149
+ merge-multiple: true
150
+ - run: python3 scripts/package_npm.py bins npm-out
151
+ - id: have
152
+ run: echo "token=${{ secrets.NPM_TOKEN != '' }}" >> "$GITHUB_OUTPUT"
153
+ - if: steps.have.outputs.token == 'true'
154
+ run: scripts/publish_npm.sh npm-out
155
+ env:
156
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
157
+ - if: steps.have.outputs.token != 'true'
158
+ run: echo "NPM_TOKEN is not set, so nothing was published to npm (the packages were built and checked)."
159
+
160
+ github-release:
161
+ needs: [build, wheels, sdist, publish-npm]
162
+ runs-on: ubuntu-latest
163
+ steps:
164
+ - uses: actions/download-artifact@v4
165
+ with:
166
+ path: files
167
+ merge-multiple: true
168
+ - uses: softprops/action-gh-release@v2
169
+ if: startsWith(github.ref, 'refs/tags/v')
170
+ with:
171
+ generate_release_notes: true
172
+ files: files/*
@@ -0,0 +1,84 @@
1
+ name: rust
2
+
3
+ # One pipeline, in order: the checks on Linux (x86 and ARM) and macOS, native Windows, and a small k6 run, all at once; then packaging
4
+ # (the pip wheel and npm packages installed in clean folders and run) only once all of those have passed, because there is no point
5
+ # packaging a build that fails its tests. Docs-only changes skip it, and a newer push replaces a run still going.
6
+ on:
7
+ workflow_dispatch:
8
+ push:
9
+ branches: [main]
10
+ paths-ignore: ["**.md", "site/**", "learnings/**", "deploy/**"]
11
+ pull_request:
12
+ paths-ignore: ["**.md", "site/**", "learnings/**", "deploy/**"]
13
+
14
+ concurrency:
15
+ group: rust-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ check:
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
24
+ runs-on: ${{ matrix.os }}
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: dtolnay/rust-toolchain@stable
28
+ with:
29
+ components: rustfmt, clippy
30
+ - uses: Swatinem/rust-cache@v2
31
+ with:
32
+ cache-on-failure: true
33
+ - run: cargo fmt --check
34
+ - run: cargo clippy --all-targets -- -D warnings
35
+ # Every integration test is one program (tests/it), so it is compiled and linked once.
36
+ - run: cargo test
37
+ # The timing tests again, many times: a race that shows once in fifty runs is still a bug.
38
+ - run: 'for i in $(seq 20); do cargo test --test it races:: -q || exit 1; done'
39
+ - run: python3 scripts/codemap.py --check
40
+ # Health checks last: every feature alive, then the shipped binary used the way a person would.
41
+ - run: cargo run -q -- selftest
42
+ - run: scripts/smoke.sh
43
+
44
+ # Windows is built and tested natively. The stand-in-agent tests need a Unix shell and skip themselves there; the lints run
45
+ # on Linux and macOS, and the Windows-only code is compiled by the test build.
46
+ windows:
47
+ runs-on: windows-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - uses: dtolnay/rust-toolchain@stable
51
+ - uses: Swatinem/rust-cache@v2
52
+ with:
53
+ cache-on-failure: true
54
+ - run: cargo test
55
+ - run: cargo run -q -- selftest
56
+
57
+ # A small k6 run (200 users with three bots each, under a minute). The big runs are in load.yml and wait for approval.
58
+ load-smoke:
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@v4
62
+ - uses: dtolnay/rust-toolchain@stable
63
+ - uses: Swatinem/rust-cache@v2
64
+ with:
65
+ cache-on-failure: true
66
+ - uses: grafana/setup-k6-action@v1
67
+ - run: cargo build --release --bin claudecord
68
+ - run: scripts/load/smoke.sh 200 target/release/claudecord
69
+
70
+ # Only once everything above has passed. macOS and Windows packaging are checked by release.yml before anything is published.
71
+ packaging:
72
+ needs: [check, windows, load-smoke]
73
+ runs-on: ubuntu-latest
74
+ steps:
75
+ - uses: actions/checkout@v4
76
+ - uses: dtolnay/rust-toolchain@stable
77
+ - uses: Swatinem/rust-cache@v2
78
+ with:
79
+ cache-on-failure: true
80
+ - uses: astral-sh/setup-uv@v7
81
+ - uses: actions/setup-node@v4
82
+ with:
83
+ node-version: 22
84
+ - run: python3 scripts/test_packaging.py
@@ -0,0 +1,23 @@
1
+ name: uptime
2
+
3
+ # An outside check of the hub, from GitHub's network every 5 minutes, which is free. A failure here means the hub could not be
4
+ # reached from the internet: DNS, TLS, the host or the hub itself. GitHub emails the repository owner when a scheduled run
5
+ # fails. Set the repository variable HUB_URL (Settings > Secrets and variables > Actions > Variables) to turn it on.
6
+ on:
7
+ schedule:
8
+ - cron: "*/5 * * * *"
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ probe:
13
+ if: vars.HUB_URL != ''
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ # Three tries, ten seconds apart, so one dropped packet is not an outage (the same debounce the prober uses).
17
+ - run: |
18
+ for i in 1 2 3; do
19
+ code=$(curl -s -o body.json -w '%{http_code}' --max-time 10 "${{ vars.HUB_URL }}/readyz" || true)
20
+ [ "$code" = "200" ] && { echo "ready"; exit 0; }
21
+ echo "try $i: HTTP $code"; cat body.json 2>/dev/null; sleep 10
22
+ done
23
+ exit 1
@@ -0,0 +1,27 @@
1
+ name: web
2
+
3
+ # The dashboard (web/) is built with Vite and the build is committed in web/dist, because the Rust program embeds it and `cargo install`
4
+ # must work without Node. This fails if the committed build is not what the source builds to, so the two cannot drift apart.
5
+ on:
6
+ push:
7
+ paths: ["web/**"]
8
+ pull_request:
9
+ paths: ["web/**"]
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ defaults:
15
+ run:
16
+ working-directory: web
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 22
22
+ cache: npm
23
+ cache-dependency-path: web/package-lock.json
24
+ - run: npm ci
25
+ - run: npm run build
26
+ - name: The committed build matches the source
27
+ run: git diff --exit-code -- dist