JSON-Rohbericht maschinenlesbar
{
"data": {
"repo": {
"topics": [
"elixir",
"go",
"shell",
"terminal",
"terraform",
"tty",
"remote-access",
"remote-control",
"remote-execution",
"self-hosted"
],
"is_fork": false,
"size_kb": 12488,
"has_wiki": false,
"homepage": "https://onlytty.com",
"languages": {
"Go": 131144,
"CSS": 29780,
"HCL": 24743,
"HTML": 13495,
"Shell": 32609,
"Elixir": 279582,
"Makefile": 4269,
"Batchfile": 47,
"Dockerfile": 3445,
"JavaScript": 107754
},
"pushed_at": "2026-07-10T04:59:54Z",
"created_at": "2026-06-20T09:07:20Z",
"owner_type": "User",
"updated_at": "2026-07-21T19:27:43Z",
"description": "Share access to your shell or specific command and control it from a browser or a phone. End-to-end encrypted.",
"is_archived": false,
"is_disabled": false,
"license_spdx": "MIT",
"default_branch": "main",
"license_spdx_raw": "MIT",
"primary_language": "Elixir",
"significant_languages": [
"Elixir",
"Go",
"JavaScript"
]
},
"owner": {
"blog": "http://dryga.com",
"name": "Andrew Dryga",
"type": "User",
"login": "AndrewDryga",
"company": "Hire me!",
"location": "Ukraine / Mexico / USA",
"followers": 213,
"avatar_url": "https://avatars.githubusercontent.com/u/1877644?v=4",
"created_at": "2012-06-21T16:37:55Z",
"is_verified": null,
"public_repos": 89,
"account_age_days": 5149
},
"license": {
"state": "standard",
"spdx_id": "MIT",
"raw_spdx": "MIT",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v0.4.10",
"kind": "patch",
"published_at": "2026-07-10T02:14:10Z"
},
{
"tag": "v0.4.9",
"kind": "patch",
"published_at": "2026-07-09T19:35:36Z"
},
{
"tag": "v0.4.7",
"kind": "patch",
"published_at": "2026-07-07T18:08:30Z"
},
{
"tag": "v0.4.5",
"kind": "patch",
"published_at": "2026-07-02T23:04:48Z"
},
{
"tag": "v0.4.4",
"kind": "patch",
"published_at": "2026-07-02T22:01:11Z"
},
{
"tag": "v0.4.3",
"kind": "patch",
"published_at": "2026-07-02T05:52:13Z"
},
{
"tag": "v0.4.2",
"kind": "patch",
"published_at": "2026-07-02T05:52:07Z"
},
{
"tag": "v0.4.1",
"kind": "patch",
"published_at": "2026-07-01T18:32:13Z"
},
{
"tag": "v0.4.0",
"kind": "minor",
"published_at": "2026-07-01T17:51:44Z"
},
{
"tag": "v0.3.0",
"kind": "minor",
"published_at": "2026-06-24T17:08:24Z"
},
{
"tag": "v0.2.5",
"kind": "patch",
"published_at": "2026-06-23T18:22:14Z"
},
{
"tag": "v0.2.4",
"kind": "patch",
"published_at": "2026-06-22T23:08:45Z"
},
{
"tag": "v0.2.3",
"kind": "patch",
"published_at": "2026-06-22T09:20:14Z"
},
{
"tag": "v0.2.2",
"kind": "patch",
"published_at": "2026-06-22T08:32:13Z"
},
{
"tag": "v0.2.1",
"kind": "patch",
"published_at": "2026-06-22T03:38:43Z"
},
{
"tag": "v0.2.0",
"kind": "minor",
"published_at": "2026-06-22T01:31:31Z"
},
{
"tag": "v0.1.0",
"kind": "minor",
"published_at": "2026-06-21T22:19:27Z"
}
],
"recent_commits": [
{
"oid": "f27b76b8d27761c812e25db45c3fb8de48cc9a3f",
"body": null,
"is_bot": false,
"headline": "chore(release): prepare v0.4.11",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T04:59:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "986913c10418feabab8fcc9c68c6a3182b031405",
"body": "On mobile the most common cause of a relay `busy` is the viewer's OWN\nhalf-dead prior socket: a wifi→LTE hop or a frozen backgrounded tab leaves\nthe old TCP dead client-side with no FIN, while the relay-side socket\nsurvives for minutes (Bandit's process-message timeout + the runner's\nkeepalive HELLO\n[…]\nnged.\n\nAdds two browser tests: a post-connect busy retries and recovers; a\nnever-connected busy stays fatal with no second socket.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(viewer): make a relay busy retryable after a network switch",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T04:44:44Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "d9a97361aa600be4578ac2e588d9ef44377119dd",
"body": "make selfhost-check does a full real-HTTPS e2e of the exact shipped\nselfhost/compose.yml + Caddyfile, but ran in no workflow — a compose\nenv-var rename or a Caddyfile change could ship broken and be found by a\nself-hoster instead of CI.\n\nAdd an advisory workflow that runs `make selfhost-check` on ub\n[…]\not a job in deploy-check.yml)\nso the heavy bundle build only fires on bundle/image changes and stays off\nthe four required checks.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "ci: cover the selfhost bundle (compose + Caddyfile) end to end",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T04:31:00Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "db9aab3490f6b6b533f80a0f9004cb20b683f523",
"body": "A hostile relay owns the socket and can force a runner reconnect, so it\ncould replay a controlling viewer's captured, still-valid CtrlReq+INPUT\nframes — re-granting control to the ghost id and re-running its keystrokes\non the host in the default `auto` control mode. That broke the \"relay\ncannot repl\n[…]\nstReplayRejectedAfterFloorResetPaths (verified red pre-fix) and\ndocuments the retention guarantee in PROTOCOL.md's Replay section.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(runner): retain viewer replay floors across reconnect and leave",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T04:27:47Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "544e54d4f5ff1e6aeecfd570766e1f8783cd199b",
"body": "…tifacts\n\nThe dispatch path took a \"tag to release\" input but never used it for\nlabeling: VERSION derived from GITHUB_REF_NAME, action-gh-release defaulted\ntag_name to github.ref_name, and docker/metadata derived semver from the run\nref — all the dispatch ref, not the input. A dispatch from main wit\n[…]\nthe run ref (gh workflow run release.yml\n--ref vX.Y.Z), so version, release tag, and image tag all agree. Tag-push path\nunchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(release): drop the workflow_dispatch ref input that mislabeled ar…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T04:17:14Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "0cc0ae6ec160995c9bb6cc5a153917d73eb8c060",
"body": "One create call could make a multi_viewer session and script unbounded\nviewer connects to /ws/viewer/:id: join_viewer's unlocked branch admitted\nevery socket with a Process.monitor and no cap, and no per-IP throttle\nguarded the upgrade — so one client could drain the shared connection\nbudget and loc\n[…]\n).\n\nTests: cap rejects the (max+1)th viewer; a second upgrade from one IP is\n429. make check (128 server) and make e2e (20) green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: cap viewers per session and throttle WS upgrades on the relay",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T04:13:51Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "d8bf791048db25c93f8b75682d12615e2af4a83e",
"body": "ONLYTTY_TRUSTED_PROXY_HOPS read the client IP at a fixed offset from the\nRIGHT of X-Forwarded-For, which is only spoof-resistant for the LB shape\n`<client>, <proxy>` (hops=1). A single edge proxy — the bundled Caddy,\nnginx's $proxy_add_x_forwarded_for — appends the CLIENT as the LAST entry,\nso no nu\n[…]\ne/N modes and an nginx recipe added.\nTests: client_ip edge key-on-last / spoof / distinctness / fallback,\nenv edge parse + reject.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: per-client rate limiting behind the bundled Caddy (edge XFF mode)",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T04:04:33Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "5ca97641aa486e509fb90147c433d9c18779382f",
"body": "…e green\n\n`make check` includes install-check, but ci.yml ran runner/web/server-check in\nseparate jobs and never install-check — an installer regression (e.g. the\n`sh -s -- claude` run-after-install path) merged green on all four required checks\nand first surfaced at the release gate. Add `make inst\n[…]\njob names stay stable for branch protection). Add the `## CI` section to\nREADME that AGENTS.md's \"README → CI\" pointer referenced.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: run the installer test in CI so installer regressions can't merg…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T02:13:00Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "27db76c9906818c888f22a42d75b8eafff762a35",
"body": "On a phone, tapping the terminal focuses xterm's hidden helper textarea,\nwhich raises the soft keyboard — but every input path (typed keys, the touch\nkey bar, pinned shortcuts, Paste) funnels into sendInput(), whose first line\nsilently dropped everything until the user separately tapped Take control\n[…]\ncontrol view-only host, typing surfaces the existing \"not granted\" status,\nthe button never goes live, and the keystroke never reaches the host.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: typing in a read-only viewer requests control instead of vanishing",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-10T02:08:06Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "8d9765044c49fab03d1795d7c585798c2a0d1a53",
"body": null,
"is_bot": false,
"headline": "fix: bump Go toolchain for release audit",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-09T19:30:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "674e30019c145119d40bd4b85c5ffd32803339ef",
"body": "…e knob\n\nThe POST /api/sessions rate limit keys on the client IP, but the production\ncloud-init never set ONLYTTY_TRUSTED_PROXY_HOPS, so it defaulted to 0 and\nkeyed on conn.remote_ip — behind the Google HTTPS LB that is a GFE address,\ncollapsing the \"30/min per IP\" limit into a fleet-wide shared buc\n[…]\n.\n\nDocs/infra only; cloud-init YAML validated. The selfhost ClientIP enhancement\nthat would close the single-edge-proxy gap is filed separately.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: enable per-client rate limiting on the hosted relay; document th…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-09T19:28:35Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "92cd562d27e5047c6e63d4f6efa32df9ac3107b4",
"body": "The accidental-paste guard tested data.slice(0, -1) for a newline, exempting\nthe final character — so a single-line paste ending in a newline (\"cmd\\n\",\nor \"cmd\\r\" after xterm's paste normalization) saw no newline, skipped the\nconfirm, and executed immediately. That is exactly the classic\npaste-a-com\n[…]\nt pastes a newline-terminated command and asserts the\nconfirm fires and a dismissed paste never reaches the host; verified against\na live relay.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: close the paste-guard bypass for a newline-terminated paste",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-09T19:23:19Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "0826d249493e2abaee1ebf68ce7034a3b5f8c442",
"body": "Run() read o.sess.ExitCode() after wg.Wait(), but wg only waits for\npumpOutput — not the exit-watcher goroutine, which is inside cmd.Wait()\nwriting cmd.ProcessState. On a SIGTERM/SIGHUP teardown the two race: Run\ncould return a torn value or -1 (ProcessState still nil) → os.Exit sees 255.\n`go test -\n[…]\nffort by design.\n\nAlso switched runner-check to `go test -race` so this class fails the gate;\nverified it flags the old read and passes the fix.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: record child exit code via an atomic to end the exit-code data race",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-09T19:16:59Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "8123d1ae035a603d2fd212a9aef3a837122d2509",
"body": "When the runner's send queue overflows under heavy child output, the sender\nfires a broadcast repaint (viewerID \"\") to resync every viewer. sendRepaint\nunconditionally appended a CONTROL frame, and controlState(\"\") resolves to\nTaken whenever anyone owns control — so the broadcast told the *current\no\n[…]\nemits no control frame (and a\ntargeted one still does); it fails on the old always-append behavior.\nrunner-check green (go vet + go test ./...).\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: don't broadcast a control frame in the drop-recovery repaint",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-09T19:14:19Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "372ff66cba98553eff3e6d0e60b24e2ec29a4226",
"body": "…he QR\n\nNew Claude Code boots into the alternate screen (ESC[?1049h ESC[2J) ~0.4s\nafter launch, hiding the QR/link banner for the whole session: the alt\nscreen has no scrollback and nothing re-prints the link, so the host had\n~0.4s to scan it. Command-mode runs now hold the launch after the banner\nw\n[…]\nter in both the stdin-TTY and /dev/tty flows and claude enters the\nalt screen only after; piped runs are unchanged. make check + make e2e\ngreen.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: hold command launch until Enter so full-screen apps can't wipe t…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-09T18:27:53Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "636b701969d7a0c96e1bfc5dba85fcc6323b7029",
"body": null,
"is_bot": false,
"headline": "fix: bump Phoenix and hpax for release audit",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-07T18:03:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1d377ac1cb8bdcfe265e8c7eb96753420afedefb",
"body": null,
"is_bot": false,
"headline": "Let installer launch a command after install",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-07T17:53:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "41d71ff2791f6b2ff2591796ee254598d15648bc",
"body": null,
"is_bot": false,
"headline": "fix: clarify onlytty host session state",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T22:48:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "482b85d40e5b77310fb2e55e5f4c7f46cf9f91e8",
"body": null,
"is_bot": false,
"headline": "chore: drop pre-v1 legacy compatibility shims",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T21:55:05Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c789ae342af1e4a268c9d86b6d7a3ea3fbcb2117",
"body": "web-check ran only the node unit tests, which import the pure modules\n(crypto/wire/keys) but never app.js — so a syntax error in the largest\nclient file passed `make check` and only surfaced as e2e timeouts\n(this happened once; see .agent history). Add a parse-only\n`node --check` loop over portal/priv/static/assets/*.js (vendor/\nexcluded by the glob) before the tests. Proven red on a planted\n`function broken( {` in app.js, then reverted.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "gate(web): syntax-check first-party viewer JS with node --check",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T11:54:41Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "a1c3db792a0bcfbbd27fa8a5316a8a13eb0a2f17",
"body": "…'t end a live session\n\nThe viewer compared the relay's absolute expires_at against the device's\nown Date.now(); a fast client clock made the remaining time go negative\nbefore the relay TTL fired, and tickTtl self-disconnected with \"session\nexpired\" even though the relay — the real authority — still\n[…]\n waited out) — the viewer must stay\nconnected showing \"expiring…\", and an injected relay-style\n{\"t\":\"bye\",\"reason\":\"expired\"} must still end it.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(viewer): make the expiry countdown display-only so clock skew can…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T11:50:46Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "383141b660473146ea175048d9c830f2b1270913",
"body": "…copy\n\n62 near-identical /control/:slug pages differed only by two sentences, and\nthe :why copy repeated one bathroom-break gag across dozens of tools — the\nclassic doorway/thin-content pattern, and off-tone for a security product.\n\nDirection C (hybrid): rewrite all 62 :why lines to concrete, distin\n[…]\nindex_slugs\nMapSet + Tools.indexable?/1. Demoted pages still render, keep a self\ncanonical, and stay linked from /tools, so nothing is orphaned.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
"is_bot": false,
"headline": "seo(site): thin the indexable tool-page set and rewrite the per-tool …",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T11:43:41Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "17a41bd6a1f9dfae32688f8657c3d8443a0ea2de",
"body": "…icit\n\nThe relay speaks plain HTTP and force_ssl trusts X-Forwarded-Proto to decide a\nrequest already arrived over HTTPS. Safe behind a TLS proxy, but a client\nreaching :4000 directly could forge the header and bypass the http->https\nredirect, serving the secret-bearing viewer page in cleartext. Not\n[…]\n bare docker run example binds 127.0.0.1:4000:4000 to\nmatch the nginx proxy_pass above it. No code change; rewrite_on left intact.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs(selfhost): make force_ssl X-Forwarded-Proto trust invariant expl…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T10:44:36Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "f227ec1c2c1d81f4a3174b0b3c3cb08e9b83a76b",
"body": "…es_at\n\nPROTOCOL.md's relay control-plane section had drifted from the code in three\nplaces. Cross-checked each against the implementation and added them:\n\n- going_away: relay sends {\"t\":\"going_away\"} on drain (session.ex:254) to both\n runner and viewer — a reconnect nudge, not a close; traffic kee\n[…]\nhat\n viewer_protocol appears on the runner hello only.\n\nSkimmed to_viewer / runner_ready while here; both still match. Docs only.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs(protocol): document going_away, replaced bye reason, hello expir…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T10:41:13Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "96dd43d6ea13bb399e93f6d1a647a98495008c1d",
"body": "\"ask\" implied the host is prompted before a viewer takes control; it\nisn't — control is auto-granted to any link-holder. Rename the default\nto \"auto\" (enum ControlAsk->ControlAuto repo-wide) and keep \"ask\" as a\ndeprecated but accepted alias so scripts don't break. Docs and error\ntext updated; wire protocol and browser unchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "refactor(runner): rename default --control ask mode to auto",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T10:38:54Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "0efdfdbd5059840926a42a5cf0321a9199f542a0",
"body": "The docs said the URL #fragment \"never leaves the browser,\" which reads as\n\"leaves no trace.\" Browsers never *send* a URL fragment to a server, but the\nsecret-bearing link persists client-side: browser history, address bar, and\ncross-device browser sync (Chrome/Safari account sync). It can't be stri\n[…]\nd, and the site \"Scan it\" step to \"never sent to\na server (but stays in history/URL — treat like a password).\" No behavior change.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs(security): note the link fragment secret leaves local traces",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T10:35:29Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "2b00eafe5b7b9fa7860025b519bffcbf5428736b",
"body": "The relay compared secrets three ways for the same job: two hand-rolled\n:crypto.hash_equals + ArgumentError rescues (session, socket_controller)\nand one direct Plug.Crypto.secure_compare/2 (metrics_access).\n\nAdd OnlyTTY.SecureCompare.equal?/2 delegating to Plug.Crypto.secure_compare/2\n(constant-time\n[…]\n copies.\n\nAdd secure_compare_test.exs covering equal, unequal same-length, unequal\ndifferent-length, empty, and non-binary inputs.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "refactor(relay): centralize constant-time secret compare",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T10:32:54Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "2efc195911d391bbbbdb122fbd657c321da7a085",
"body": "POST /api/sessions throttled on the full client address. For IPv6 a\nsingle client is handed a whole /64, so an attacker could rotate source\naddresses within their prefix to sidestep \"30 creates/min per IP\" and\ninflate the limiter's ETS table with one {key, bucket} per address.\n\nAdd mask/1 in ClientI\n[…]\ndirect\npaths mask. Scoped to the returned key only — conn.remote_ip is never\ntouched, so the /metrics loopback gate is unaffected.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(relay): bucket IPv6 rate-limit keys to /64",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T06:35:18Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "0c8dc93c294b36b5631e509855639e2a9717b396",
"body": "The runner keyed control ownership and per-viewer replay floors on the\nrelay-assigned viewer_id. A hostile relay could assign a second link-holder\nthe SAME id as the current control owner and promote it to control,\nweakening the `once` \"first taker only\" guarantee.\n\nEach viewer now generates its own\n[…]\nted as a fresh\nviewer (new self id -> repaint); the legacy empty-id single-viewer path is\nunchanged. PROTOCOL.md updated to match.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(security): bind control ownership to a viewer-chosen id",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T06:32:13Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "ad0531645e93289e187f14658588cb34d5bd80af",
"body": "The ring drops oldest bytes at an arbitrary byte offset, so a viewer\njoin/reconnect repaint could begin partway through a multi-byte rune,\nshowing garbage glyphs at the top of the first screen (worse on\nreconnect-heavy mobile use).\n\nSkip leading UTF-8 continuation bytes (b&0xC0 == 0x80) in Ring.Snap\n[…]\nentionally omitted:\nsendRepaint already prepends \\x1bc (RIS full reset), which neutralizes a\ndangling partial escape at the front.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(runner): align repaint snapshot to a UTF-8 rune boundary",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T06:16:45Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "ef9b35166e2291c31f38f6d9b7ce6de1bc68b686",
"body": "A viewer that stops draining its socket applied no backpressure to the\nrunner: the relay's fan-out uses async send/2, so runner output piled\ninto the stalled viewer's process mailbox unbounded — one connection\ncould push a relay node toward OOM.\n\nAdd a per-viewer backlog watermark on the viewer rece\n[…]\ntuffed mailbox\n(real TCP backpressure isn't reproducible in-suite). Gates: make check\ngreen (112 portal tests) and make e2e green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(relay): evict slow viewers to bound fan-out backlog",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T06:13:56Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "43aa247a42d2ba93d25247d73969b268edf64d9d",
"body": null,
"is_bot": false,
"headline": "fix: make mobile hero first paint static",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T05:46:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "fb7f4a8f22c2f5e8d5bb41d8c8933df18b8bd227",
"body": null,
"is_bot": false,
"headline": "feat: support multi-viewer control handoff",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T01:35:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "21047427d3087351ef4588afa353c61743dcf4fa",
"body": null,
"is_bot": false,
"headline": "ci: make image promotion manual",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-02T01:34:48Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "baf422fab4348f285b1044a568e41bfc31048495",
"body": null,
"is_bot": false,
"headline": "Add instance_redistribution_type NONE to MIG update policy",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T20:02:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a8fbd0f6b169d51b4df887aab8dde671ee7a148d",
"body": "Prevent allocation failures and boot-time races:\n\n- Set distribution_policy_target_shape = \"BALANCED\" so GCE can\n avoid zones with insufficient small-VM capacity during rollouts\n- Add explicit depends_on edges for NAT, firewall rules, and IAM\n grants so instances never start before their runtime prerequisites\n are fully converged",
"is_bot": false,
"headline": "Add BALANCED distribution policy and explicit MIG dependencies",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T19:34:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2a9e12ba68290d55e79d052718d61acb0c2224aa",
"body": null,
"is_bot": false,
"headline": "chore(release): v0.4.1",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T18:27:28Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5278b6011c38a54213184c7bd53a1fc8c1888926",
"body": "Bump the portal version to 0.4.0 and clear release audit blockers by updating Sentry/Hackney/Plug and removing the test-only gun/cowlib dependency.\n\nReplace the gun-based WebSocket test helper with a small raw TCP WebSocket client so relay tests still exercise Bandit/WebSock over real sockets withou\n[…]\nnerable test-only deps.\n\nLocal gates: make check; PATH=\"$(go env GOPATH)/bin:$PATH\" make audit; MIX_ENV=prod PHX_SERVER=true PHX_HOST=localhost PORT=4001 ONLYTTY_SERVER=http://127.0.0.1:4001 make e2e.",
"is_bot": false,
"headline": "chore(release): v0.4.0",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T17:47:27Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ead501358ad5365b971a91819a7a9dd614b3b5ca",
"body": "Extract all `ONLYTTY_*` env parsing from `runtime.exs` into\n`Onlytty.Env.runtime_overrides/1`, which accepts an injectable\n`get_env` function. `runtime.exs` now calls it once and applies\nthe resulting keyword list.\n\nAdd `Onlytty.Test.RuntimeEnv.with_runtime_env/2` so tests drive\nthe same parse/validate path that deployments use, instead of\nsetting app env directly. Tests that formerly mutated app env\nnow use this helper, which saves and restores both process env\nand app env around each case.",
"is_bot": false,
"headline": "Centralize runtime env parsing and fix test isolation",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T17:36:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b5f6230a160ee671b84308982493ba4cebae1b04",
"body": "The bottom touch key bar scrolls horizontally with a hidden scrollbar, so\nat phone width ^C/^D and pinned shortcuts scrolled off-screen with no\naffordance — the interrupt flow could be undiscoverable. Fade whichever\nedge still has keys off-screen (of-l/of-r toggled from scroll position;\nmask-image is paint-only, so no layout shift and taps still land).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(viewer): fade the mobile keys bar's overflowing edges",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T17:21:45Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "407e9ae833bb8ecb51e0dd9745ef890d0270ad60",
"body": "The browser viewer reconnects forever once it has connected, so it survives\ndeploys and relay-node loss while the runner re-claims the session. But the\ngive-up path (failCount >= 3) was gated on !everConnected, so a no-expiry\nsession whose relay node hard-crashed — and whose runner will never reclai\n[…]\nut a transient blip to reconnect. The\nnever-connected fast give-up is unchanged. make check (106) and make e2e (15,\nwas 13) green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(viewer): cap reconnects after a session goes missing for good",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T16:54:41Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "f76e3cab2e9737c2b78526fc2db7c07d1d8501a0",
"body": "…proxy\n\nBehind the hosted Google HTTPS LB, conn.remote_ip is the LB/GFE, not the client,\nso \"N creates/min per IP\" collapsed into one global bucket — one client could\n429 everyone, and normal growth could false-positive.\n\nAdd OnlyttyWeb.ClientIP.resolve/1, used by RateLimitGuard for the throttle key\n[…]\nhat hops=1 buckets per real\nclient while hops=0 can't be multiplied by a spoofed header. make check (106)\nand make e2e (13) green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(relay): key create rate limiting on the real client IP behind a …",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T16:33:21Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "27d629686d7c077934bafd2fcf19bfbf0424f836",
"body": "The 404 re-claim path (relay node drained/replaced, or a full-cluster loss)\nre-created the session with the original `--ttl`. For a TTL session that meant a\nresurrected session got a fresh full lifetime and outlived the user's intended\ndeadline — a violation of the TTL contract, even if only in the \n[…]\n-driven\nTestReclaimTTL covers remaining / below-min / sub-second / at- and\npast-deadline. make check (95) and make e2e (13) green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(runner): preserve the TTL deadline when re-claiming a lost session",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T16:26:05Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "5bf0d63952236b87d95cc6d6721bfd1e52468695",
"body": "The latent `locked: false` branch overwrote the single `state.viewer` slot on a\nsecond viewer join, leaking the first viewer's monitor and leaving it wired to\nthe runner — so it could never actually support multiple viewers.\n\nModel viewers as a set instead:\n\n- `Onlytty.Session` state: `viewer`/`view\n[…]\noves\nbyte-level broadcast to two viewers and that each viewer's input reaches the\nrunner. make check (95) and make e2e (13) green.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "refactor(relay): model viewers as a set before unlocked sessions",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T16:21:21Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "ccd9d9ebbb3a291d2975d69edd58821a5e221a48",
"body": "…sh Copilot copy\n\nThree parked low-risk nits:\n\n- Viewer buttons: the viewer has no <form>, so a default type=\"submit\" is\n harmless today but brittle. Set type=\"button\" on every non-submit button in\n viewer.html and the ones app.js builds dynamically.\n- Home page: removed the id=\"tools\" anchor — no\n[…]\ninst GitHub docs), so `cmd` was already correct; refreshed the\n stale explain/suggest description to its current agentic framing.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore(cleanup): explicit button types, drop dead #tools anchor, refre…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T16:09:35Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "694da80f781bb67cc213ec60ce86cc2f41e195a0",
"body": null,
"is_bot": false,
"headline": "Convert skills to adopt coop v3",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T15:54:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4fac7c0bd02e1f9bafab844a0547a09718140180",
"body": null,
"is_bot": false,
"headline": "fix(site): make mobile hero paint reliably",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-07-01T15:53:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "db76e96ceedba611a30f6e87ecd71430f2c89133",
"body": "Turnkey self-hosting + the first multi-arch relay image.\n\n- selfhost/ bundle (Docker Compose + Caddy automatic HTTPS) + SELF_HOSTING.md,\n verified end-to-end via `make selfhost-check`\n- relay image now built for linux/amd64 + linux/arm64\n- /self-hosting marketing page, linked from the nav, FAQ, and\n[…]\nter\n- fix: button text stays readable on hover (was light-on-light)\n- docs: corrected portal/README TTL defaults to match the code\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore(release): v0.3.0",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-24T17:04:15Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "ca75208c3fe9fa376ab2d594b9506367bd1213f6",
"body": "The .btn-* are <a> tags, so the global `a:hover { color: #b7f2fb }` (specificity\n0,1,1) beat `.btn-primary { color: var(--ink) }` (0,1,0) on hover — flipping the\ndark ink text to light cyan on the bright cyan fill, i.e. unreadable. Affected\nevery primary button site-wide (nav \"Get started\", \"Get the\n[…]\n,27) (--ink), not rgb(183,242,251). site.css is content-hashed\ninto the asset cache-buster, so returning visitors pick up the fix.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): keep button text readable on hover (was light-on-light)",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-24T16:53:28Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "c81975531d29dd21e7f4edcb62bf0fe9d0fa5f3c",
"body": "The steps jumped straight to `cp .env.example .env` without saying where the\nbundle comes from. Add a \"Get the bundle\" step (git clone … && cd onlytty/selfhost,\nmatching SELF_HOSTING.md) and fix the now-inaccurate \"Copy two files\" heading\nto \"Clone the bundle, set two values, bring it up\".\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): self-hosting quick-start was missing the \"get the files\" step",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-24T16:41:55Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "36503e5f2261a42fb2ce845a0183bfce4fe8e2e9",
"body": "It was only in the footer + FAQ, but /self-hosting is a top-level page just like\n/tools, which is in the nav — so it belongs there too for wayfinding and as an\nopen-source trust signal. No mobile cost: .nav-links is display:none below 600px,\nso the desktop links never touch the small-phone nav (brand + GitHub + Get started).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(site): add Self-hosting to the primary nav",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-24T16:29:23Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "7b13e168f9160f6618f0d013cfc72962d5520039",
"body": "Add a server-rendered, indexable /self-hosting page (PageController :self_hosting\n-> Page.self_hosting/0) that answers the \"self-host terminal sharing\" intent: the\none-command Compose+Caddy path, why-self-host (own domain / limits / network /\ncode-delivery trust), what-you-run (stateless, no DB, beh\n[…]\nlists it next\nto Protocol / Security model. site_test covers the new page + footer link, and\nthe sitemap count bumps to Tools + 6.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(site): self-hosting landing page, linked from the FAQ and footer",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-24T16:26:16Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "d2c6257facc6c9f23695545be34a6c8d25716d38",
"body": "The portal-image job built amd64 only, so self-hosters on arm64 (Graviton,\nAmpere, Apple Silicon) who pull ghcr.io/.../onlytty hit a prim_tty NIF crash\nunder QEMU emulation. Add docker/setup-qemu-action + setup-buildx-action and\nbuild `platforms: linux/amd64,linux/arm64`. The Dockerfile already uses\n[…]\n\n\nTakes effect on the next tagged release (the arm64 leg compiles the Elixir\nrelease under emulation, so release runs are slower).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "ci(release): publish a multi-arch (amd64+arm64) relay image",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-24T16:01:14Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "6afd21b244da0aa13505a8e743bf8f536e2772ec",
"body": "Add a copy-paste Docker Compose + Caddy bundle (selfhost/) that brings the\nrelay up with automatic HTTPS in one `docker compose up -d`, plus a\nSELF_HOSTING.md guide: the breeze path, bring-your-own-proxy\n(nginx/Cloudflare/Fly), the full config reference, operating notes, and the\ncode-delivery trust \n[…]\nv table out of the\nroot README (it now points to the guide). gitignore selfhost/.env so a real\nSECRET_KEY_BASE is never committed.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(selfhost): one-command self-hosting bundle + tested guide",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-24T16:01:05Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "b88f7ef14897a75e57df56529f9c559f6f530353",
"body": "…ive downgrade)\n\nMixpanel's official snippet injects the library with a protocol-relative URL\n(//cdn.mxpnl.com/...), which resolves to http:// on a plaintext origin. The\nmarketing CSP allows script-src https://cdn.mxpnl.com only, so the e2e CSP guard\n(dev/test/browser/site.test.js, served over http:\n[…]\ny origin scheme.\n\nVerified by replaying the e2e guard over http with the exact marketing CSP: zero\nsecuritypolicyviolation events.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): load the Mixpanel library over https (was a protocol-relat…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-23T18:18:08Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "357a762f38f1a7802b6419f583c47ebab38c8a87",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore(release): v0.2.5",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-23T18:09:06Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "8a1ce97d4e698aa4fe9201c43abe3b9c1a89a1d7",
"body": "…ed\")\n\nThe custom loader fetched mixpanel-2-latest.min.js directly, but the modern\nlibrary requires Mixpanel's stub snippet to be present first — loading it bare\nlogged '\"mixpanel\" object not initialized' and left window.mixpanel undefined\n(seen live on onlytty.com). Replace the loader with Mixpanel\n[…]\nl.__loaded === true,\nget_distinct_id present) with no error — with api.mixpanel.com blocked so no test\nevents reached the project.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): use Mixpanel's official loader snippet (was \"not initializ…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-23T17:53:28Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "534dd58172691824db75eab8b4008c23e8b6f3e1",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore(release): v0.2.4",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T23:04:37Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "588ee2e72c8722b2c4858a9a6cf5e58be299ea9a",
"body": "Adds product analytics (page views) to the marketing site — deliberately NOT to\nthe terminal viewer, where a tracker could leak the link's #fragment secret.\n\n- /assets/mixpanel.js: a first-party loader (keeps script-src 'self', no inline),\n honors Do Not Track, persists in localStorage (not cookies\n[…]\n assert the CSP split (analytics on /, strict on /s/:id), the loader is\n served + unreferenced by the viewer, and the disclosure.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(site): Mixpanel analytics on the public marketing pages only",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T23:01:10Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "3bccf47d38b5cc84be6a3bd3f438c4089f6d8702",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore(release): v0.2.3",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T09:16:13Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "aaf34746aae9974f5fe2cd132fc1001284138b61",
"body": "Move the phone viewer from centered to the terminal's bottom-right corner (like\nthe desktop overlay), so the QR and banner stay fully visible on the left.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): anchor the mobile hero phone to the terminal's bottom-right",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T09:15:02Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "da98c13099efe0fc4c5bac99a6fbac804bd54c28",
"body": "Slightly larger phone viewer over the terminal background; proportions kept via\naspect-ratio. Still smaller than the terminal, which remains the backdrop.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): enlarge the mobile hero phone ~30% (130->169px)",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T09:12:54Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "988b00a8abd3d706fd8730b3d3b5ca513c90bc8c",
"body": "Phone width 260->130px (proportions preserved by aspect-ratio). Now that the\nphone is smaller than the terminal, the terminal sets the stage height again (in\nflow, as the background) and the small phone is centered absolutely on top of it.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): make the mobile hero phone 2x smaller over the terminal",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:51:07Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "c153c21e5e4a69278ce3b104000c01db9c4ae7d2",
"body": "…al behind)\n\nInstead of stacking, the phone viewer (kept large) now sits on top of the\nterminal window, which recedes to the background. The phone is in flow (it's the\ntaller element, so it sets the stage height); the terminal is centered absolutely\nbehind it. Mobile only — desktop keeps the corner overlay.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): on mobile, layer the large phone over the terminal (termin…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:46:37Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "4ffbde2705104f5482c0125776d11261ad09faa9",
"body": "The 132px corner overlay cramped the viewer's text and crumbled its key bar, and\na larger phone can't overlap the narrow mobile terminal cleanly. On phones, stack\nthe terminal (with its QR) above a larger centered phone (up to 260px) so the\nsession log and the Esc/Tab/Ctrl/^C bar read cleanly. Desktop unchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): bigger, centered phone viewer in the mobile hero",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:40:23Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "7fbfff2ecfdec7f5f299967d8c6eb6784323f85c",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore(release): v0.2.2",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:27:54Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "8231f1744d3d58583cb67baa8cad65ac072f75ae",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "content(site): nudge the start-section CTA (\"try it, under a minute\")",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:27:22Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "c200cabbd9e0c79201178b250ab14b6b37c11191",
"body": "Restore the desktop overlay look on phones — the phone viewer sits over the\nterminal's bottom-right corner (sized down to 132px so it doesn't bury the QR)\ninstead of stacking below it.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): keep the phone overlapping the terminal on mobile",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:22:12Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "feed218ef658cc1ccd8e9106efb8e945806ce3bf",
"body": "The previous change hid the desktop terminal on phones, which also hid the\nQR/link banner. Instead, at <=640px stack the terminal (with its QR) and the\nphone viewer un-overlapped so both stay visible. Desktop keeps the overlapping\nlayout.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): on mobile show both the terminal (QR) and the phone, stacked",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:19:42Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "6d0ccb0b7b6b9511fd222c34804b3adf12e2fec2",
"body": "…ro on mobile\n\nOn phones the hero showed the desktop terminal window, which reads wrong on a\nphone. Below 640px, hide .term and show the .phone viewer mock standalone\n(centered, static) — the product on the device you're actually holding. Desktop\nkeeps the terminal window with the phone overlay. Applies to tool heroes too\n(shared term_demo).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): show the phone viewer, not the desktop terminal, in the he…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:13:09Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "369782103ae37a7e9dc8ba13abd44880cd96ddfd",
"body": "Drop the .stage::before radial glow and the accent-glow layer in the .term\nbox-shadow. The terminal keeps its window drop shadow but no longer sits in a\ncyan halo — cleaner against the dark background. (Applies on tool pages too,\nwhich share .stage.)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): remove the cyan glow behind the hero terminal",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T08:08:53Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "8fc19e99225612358a7a3e512e9d2e69dd7478a1",
"body": "The global display h1 (up to 4.6rem) crowded the hero's split column and read as\noverloaded. Scope a smaller clamp to .hero h1 (max 3.5rem, slightly looser\ntracking); the headline now wraps to three lines with breathing room. Rendered\ndesktop + mobile.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): calmer hero headline so it doesn't feel overloaded",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T07:58:23Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "d1c7646f413d1689740f33838aa814ad99520158",
"body": "…shadows\n\nRedesign feedback:\n- Hero: back to the split layout (copy left, the real onlytty terminal right; on\n mobile it drops below). The custom wide hero terminal is replaced by the\n product-accurate term_demo — the banner reads top-to-bottom (command, header,\n scannable QR, then Link/Expires b\n[…]\n shadow (it's a window).\n- Remove the now-dead hero_term + ht-*/deck/stage CSS.\n\nmake check green (85); rendered desktop + mobile.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): split hero back, product-accurate terminal, flatten block …",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T07:49:18Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "5269742974757aaad49a43ef1dec8716909420e1",
"body": "Agency-style redesign via the creative-director method (brief → territories →\nchosen direction → rendered passes). The site now reads as the onlytty program's\nown output instead of generic SaaS furniture, retiring the split-hero,\ncentered-3-card, and glow-backdrop clichés.\n\n- Hero: a left-aligned ed\n[…]\n reviewed at 1440/1024/390 (desktop + mobile): no overflow, no JS\nerrors. make check green, with a test for the new proof section.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "redesign(site): \"standard output\" — a terminal-first editorial home",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T05:05:29Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "53d53276a3d98ef6d830faec1503861a07971343",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore(release): v0.2.1",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T03:35:03Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "604c225d4b678faea4daeab8a074a1d62e540b76",
"body": "…ttons\n\nOn touch, .btn-sm (nav GitHub / Get started) and .copy computed to ~29–34px,\nunder the WCAG 2.5.5 44px minimum and on the primary mobile conversion path.\nAdd a (pointer: coarse) min-height: 44px.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): meet the 44px tap-target floor for the small nav + copy bu…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T03:08:51Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "1905f23138feb07c196e160bcfb7e52e6f03b477",
"body": "The hero/tool demos labeled the default session \"read-only\", but the default\n(--control ask) auto-grants control to any viewer on request — the opposite of\nwhat the demo implied (and what the rest of the site correctly states). Drop the\ntag from the terminal banner; the phone now shows \"view-only · \n[…]\nl landing state) and \"single viewer\" (the single-viewer lock),\nand the demo's alt text no longer claims you're already in control.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "content(site): fix the demo's \"read-only\" overclaim of the default",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T03:08:51Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "45d532ba198fad399cdafa72f2312e77c8f6d373",
"body": "Review of the product's live surface found several gaps:\n- The session-fingerprint chip shipped `hidden` and was never revealed — the\n trust signal only flashed in the verify dialog. Reveal it once keys derive,\n style it, and make it tap/Enter to re-open verify.\n- \"They don't match\" only warned, t\n[…]\nrendering the viewer: fingerprint visible, overlay traps focus and\ninerts the background, no JS errors, bar layout holds at 390px.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(viewer): harden the trust surface + a11y of the mobile viewer",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T03:05:40Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "47c9c5bf784158f578d265daeae02739f2590091",
"body": "…t 500\n\nTwo ways an unauthenticated POST /api/sessions could become a 500 (+ crash-log\nspam) on the public relay, both found in review:\n\n- An absurd ttl_seconds (multi-millennium) made ttl*1000 overflow the BEAM timer\n → ArgumentError in Session.init → unmatched start_child error → 500. Add a\n har\n[…]\nroc exit\n surfaced as 500. Catch it → {:error, :unavailable} → 503, which the runner\n retries (the :global name clears shortly).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(portal): bound ttl + handle the create race so /api/sessions can'…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T02:56:41Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "531bad622facd63b270393b8c12bbe5fe14e19e6",
"body": "…claimer\n\nOnlyTTY is in production; the draft-stage hedge appended to every legal page\n(Terms/Privacy/Acceptable Use) no longer belongs. The pages keep their actual\nterms; only the meta-disclaimer is removed.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "content(site): drop the \"not legal advice / review before launch\" dis…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T02:42:02Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "ab5ca6d79524190c8cbb682aae274e9150b096cc",
"body": "The OG card showed `$ relay -- claude` (the old binary name) on every social\nshare. The hand-export had no scripted source, so replace it with a reproducible\ngenerator: dev/brand/og.html (built from the live design tokens + the app icon)\nrendered by dev/brand/og-render.mjs (Playwright 2× → sips 1200\n[…]\nal layout; now shows `$ onlytty -- claude`, ~67KB.\nBrand README documents the pipeline; banner.png noted as the superseded source.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "brand(og): regenerate the social card with the current `onlytty` command",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T02:00:42Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "eb6f7e5394855dd5970d46d43d480ae8dddc7b6b",
"body": "`go install github.com/AndrewDryga/onlytty/runner@latest` installed a binary\nnamed `runner` (Go names it after the import path's last element), so the\ndocumented \"go install … then run onlytty\" path was broken. Move package main\nfrom the module root into runner/cmd/onlytty/ — now\n`go install …/runne\n[…]\nthe new path (caveat dropped); the marketing site\nstays curl|sh-only. No CLI behavior change; internal packages and tests unmoved.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "build(runner): move main to cmd/onlytty so `go install` yields `onlytty`",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T01:53:10Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "e8add83f3518e98b2e22c374c2e9bd1862082395",
"body": "The sticky header (brand + GitHub + Get started) had a ~388px min-content floor,\nso on narrow phones (320–375px) the page was 13–28px wider than the viewport and\n\"Get started\" clipped. On ≤400px make the GitHub button icon-only (its new\naria-label keeps the accessible name) and tighten the brand↔cta gap. Verified\ndocument width == viewport at 320/360/375/390; desktop nav unchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): keep the nav from overflowing on ≤388px phones",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T01:49:26Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "fb43318d150c7e146dad6f9901028f390398e538",
"body": "The v0.2.0 release run warned these three still target Node 20 (forced onto 24\nnow; will hard-fail when GitHub drops it). Pin to current node24 releases —\ninputs verified unchanged against each action.yml (files / registry+username+\npassword / context+push+tags+labels):\n softprops/action-gh-release\n[…]\nr release warning (\"xref: [exclude:]\" deprecated) is from deps\n(phoenix/plug/sentry/castore), not our mix.exs — left for upstream.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "ci(release): bump the Node 20 actions to current node24 versions",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T01:45:07Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "66f5e7bcf0b4e605a2cc74d0046fe36d6fdf92ca",
"body": "The endpoint plugged Plug.Session with a hardcoded signing_salt (\"nPKh7BWO\")\nand the rename-residue key \"_relay_key\", but nothing reads the session: the\nrelay has no accounts, and there's no fetch_session/protect_from_forgery\nanywhere (the router is :api/:raw only). Remove the dead plug + @session_options\nso a hardcoded secret isn't shipped in a public repo.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "security(portal): drop the dead cookie session + hardcoded signing salt",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T01:41:30Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "c8545491500cff7eaa87d03dfb5bbe34065debdc",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore(release): v0.2.0",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T01:27:00Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "656828971beda5b03f3b5e6eb8e15b0c90afae0d",
"body": "- Install: `curl -fsSL https://onlytty.com/install.sh | sh` is now the\n consistent install across the hero, the start section, and every tool page\n (the inner pages showed `go install …/runner@latest`, which installs the\n binary as `runner`, not `onlytty`). README switched to the branded\n onlytt\n[…]\nhone status (no expiry). Legal/FAQ already stated this correctly.\n- Tests assert the installer renders on the home and tool pages.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs(site): lead with the install.sh one-liner + freshen copy",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T01:26:42Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "716fda2e8ae62fef0ba79d6d4c84b694c45f347d",
"body": "The hero grid's single mobile column (1fr) couldn't shrink below the install\nsnippet's intrinsic width, so the long `curl … | sh` line floored the column at\n~509px and the whole hero was clipped at the right edge on phones.\n\nLet both columns shrink (.hero-copy, .hero-demo { min-width: 0 }) and let t\n[…]\nnippet's code scroll horizontally instead of flooring the layout\n(.snippet code { min-width: 0 }). Verified rendered at 320–430px.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(site): stop the mobile hero from overflowing the viewport",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T01:26:34Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "dea554a8646cab6bbe69888a0a7514c59b91caeb",
"body": "`onlytty -- <cmd>` now works with zero config: when neither --server nor\nONLYTTY_SERVER is set, the runner connects to the hosted relay at\nhttps://onlytty.com (self-hosters still override via --server/ONLYTTY_SERVER).\nThe relay only ever sees ciphertext, so a shared default is safe.\n\nAdds envOr() an\n[…]\nat the default passes the same https/host validation\nas a user-supplied --server, so a fat-fingered constant can't break the flow.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(runner): default the relay to onlytty.com when unset",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T01:26:27Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "9dd86765e0376a83093816d5a8ca1430e83e1b3c",
"body": "… check\n\nProduction served 503 — the global LB marked every backend UNHEALTHY though the\nrelay answered /healthz=200 on the box. Two stacked causes, both blocking the\nLB's plain-HTTP probe to the instance's :4000:\n\n- Container-Optimized OS ships `iptables -P INPUT DROP` (only lo/icmp/\n ESTABLISHED/\n[…]\nfied live as hotfixes first (gcloud HC --host + iptables on the VMs):\nbackends HEALTHY, https /healthz + / = 200, nodes clustered.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(infra): open COS host firewall + Host:localhost for the LB health…",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-22T00:09:18Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "135373da4656d9d37e6961cee590568d14ab2932",
"body": "Add wait_for_instances + wait_for_instances_status=UPDATED to the regional MIG so\napply waits for the new version to fully roll out and every instance to be stable.\nCombined with the auto_healing /healthz check, an instance that never goes healthy\nkeeps the group unstable and fails the apply (bounde\n[…]\n exist\nfirst — README setup reordered with a -target secret-bootstrap step and the\ncloudresourcemanager/serviceusage prerequisite.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "infra: block terraform apply until the MIG rolls out healthy",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-21T23:21:26Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "693b0a66caa1e753795d71f58e2a9c4a4ce39b00",
"body": "systemd failed at EXEC spawning /var/lib/onlytty/start.sh with \"Permission\ndenied\": Container-Optimized OS mounts /var noexec, so it can't execve the\nscript even though it's 0755. Run it through bash (ExecStart=/bin/bash ...) —\nreading the script from /var is allowed and bash is on the exec rootfs.\nVerified on the instance: /var is rw,nosuid,nodev,noexec and /bin/bash exists.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "infra: run the relay start script via bash (COS mounts /var noexec)",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-21T23:11:08Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "1448ed596a67c97038e5ca19337dd665fb1624e5",
"body": "Delegating nameservers to the Cloud DNS zone makes it authoritative, so the\npre-existing email/verification records must live there or mail + Google domain\nverification break at cutover. Add the Google Workspace MX, the apex SPF +\nsite-verification TXT, the _spfm SPF include, and the _dmarc DMARC re\n[…]\nr records from the old zone too (esp. a Workspace DKIM\nTXT at google._domainkey, and www/subdomains) before switching nameservers.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "infra: replicate email + verification DNS into the Cloud DNS zone",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-21T23:02:11Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "51efc8eebad0711754676298192daa36dd470fe1",
"body": "Move the Authorization header off curl's command line into a 0600 curl --config\nfile, so the token can't leak via a process listing or xtrace. Guard the secret\nwrite with `{ set +x; }` so it's never echoed even if tracing is later enabled.\ncurl stays -sS (no -v) and the token is never echoed. GitHub already masks the\nsecret in logs — this is defense in depth.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "deploy.yml: keep TF_API_TOKEN out of argv (no-leak hardening)",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-21T22:37:55Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "e1a9660b09e17bb9fff294b3605f884228be25da",
"body": "Queuing the run failed with only \"curl: (22) 404\" because `curl -f` swallowed the\nresponse body. Wrap the TFC calls in a tf() helper that prints the actual error\nbody, with a hint that a 404 on POST /runs means the token can't queue runs (use\na user/team token with Apply access — an org token can write vars but not create\nruns). Also document the GITHUB_TOKEN release-trigger limitation. Happy path\nunchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "deploy.yml: surface TFC API errors instead of a bare exit code",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-21T22:30:58Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "ba30e8e10752195e162002bca9350ff832ea0bba",
"body": "Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.10.0 to 6.1.0.\n- [Release notes](https://github.com/docker/metadata-action/releases)\n- [Commits](https://github.com/docker/metadata-action/compare/c299e40c65443455700f0fdfc63efafe5b349051...80c7e94dd9b9319bd5eb7a0e0fe92\n[…]\nemver-major\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>\nCo-authored-by: Andrew Dryga <andrew@dryga.com>",
"is_bot": true,
"headline": "build(deps): bump docker/metadata-action from 5.10.0 to 6.1.0 (#2)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-06-21T22:07:05Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a0ab89da6fc852b3b779f918f8a0915aae234509",
"body": "… (#4)\n\nBumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.4.0 to 4.1.0.\n- [Release notes](https://github.com/actions/attest-build-provenance/releases)\n- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)\n- [Commits](h\n[…]\nemver-major\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>\nCo-authored-by: Andrew Dryga <andrew@dryga.com>",
"is_bot": true,
"headline": "build(deps): bump actions/attest-build-provenance from 2.4.0 to 4.1.0…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-06-21T22:07:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a062b1e1dfe3138b63e8947770a313372e6c0990",
"body": "POST /api/sessions now requires a runner-supplied id + runner_token, so the\nempty-body creates in deploy-check.sh (smoke) and load.sh (load test) 400'd —\nthe Deploy-check workflow had been red since the claim-based change. Generate an\nid+token for the smoke check; in load.sh generate a fresh pair per request so\neach POST creates a new session. Follow-up to 6ca05d4.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "Fix deploy-check + load scripts for claim-based create",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-21T21:57:21Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "f6c25fa3cea8d78fba28eccc832f1038cd0d64cb",
"body": "Replace the out-of-band `gcloud rolling-action replace` in deploy.yml with a\nTFC-driven deploy: on workflow_dispatch(version) or a published release, set the\n`container_image` workspace variable to ghcr.io/<owner>/onlytty:<version> via the\nTFC API and queue a run; a human approves it in TFC (\"Confir\n[…]\nlice B).\nRemoves the GCP WIF auth path. No-ops without TF_API_TOKEN. README updated.\n\nSlice C of the A/B/C deploy-resilience goal.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "Deploy via Terraform Cloud, approved with Confirm & Apply",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-21T21:52:53Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "84ed857752549f4e5debc54a9aa6a78f4e89a323",
"body": "On SIGTERM (a deploy draining the instance) the relay now flips /healthz to 503\n(LB stops new connections), tells connected runners/viewers the node is going\naway (they reconnect elsewhere — claim-based resume re-establishes the session),\nwaits a grace, then stops — instead of hard-cutting sockets.\n\n[…]\n\n\nmake check green (portal 81 incl. drain_test); terraform validate clean. e2e in\nCI. Slice B of the A/B/C deploy-resilience goal.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "Graceful drain on SIGTERM: seamless deploys",
"author_name": "Andrew Dryga",
"author_login": "AndrewDryga",
"committed_at": "2026-06-21T21:48:39Z",
"body_truncated": true,
"is_coding_agent": true
}
],
"releases_count": 17,
"commits_last_year": 271,
"latest_release_at": "2026-07-10T02:14:10Z",
"latest_release_tag": "v0.4.10",
"releases_from_tags": false,
"days_since_last_push": 18,
"active_weeks_last_year": 4,
"days_since_latest_release": 18,
"mean_days_between_releases": 1.7
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": false,
"health_percentage": 57,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "github.com/AndrewDryga/onlytty",
"exists": true,
"license": null,
"keywords": [],
"ecosystem": "go",
"matches_repo": true,
"registry_url": "https://pkg.go.dev/github.com/AndrewDryga/onlytty",
"is_deprecated": false,
"latest_version": "v0.4.11",
"repository_url": "https://github.com/AndrewDryga/onlytty",
"versions_count": 20,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": null,
"first_published_at": null,
"latest_published_at": "2026-07-10T04:59:37Z",
"latest_version_yanked": null,
"days_since_latest_publish": 18
}
]
},
"popularity": {
"forks": 0,
"stars": 2,
"watchers": 0,
"fork_history": {
"days": [],
"complete": true,
"collected": 0,
"total_forks": 0
},
"star_history": null,
"open_issues_and_prs": 0
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [],
"has_llms_txt": false,
"has_dockerfile": true,
"has_mcp_signal": false,
"bootstrap_files": [
"Makefile"
],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [],
"toolchain_manifests": [
"portal/mix.exs",
"runner/go.mod"
],
"largest_source_bytes": 55861,
"source_files_sampled": 80,
"oversized_source_files": 0,
"agent_instruction_files": [
"AGENTS.md",
"CLAUDE.md",
"GEMINI.md"
],
"agent_instruction_max_bytes": 5586
},
"dependencies": {
"manifests": [
"package.json",
"portal/mix.exs",
"runner/go.mod"
],
"advisories": {
"error": null,
"scope": null,
"source": null,
"findings": [],
"collected": false,
"malicious": [],
"truncated": false,
"by_severity": {},
"advisory_count": 0,
"affected_count": 0,
"assessed_count": 0,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 0,
"direct_affected_count": 0
},
"ecosystems": [
"go",
"hex",
"npm"
],
"dependencies": [
{
"name": "phoenix",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 1.8"
},
{
"name": "websock_adapter",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 0.6"
},
{
"name": "telemetry_metrics",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 1.0"
},
{
"name": "telemetry_poller",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 1.0"
},
{
"name": "jason",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 1.2"
},
{
"name": "libcluster",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 3.5"
},
{
"name": "bandit",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 1.5"
},
{
"name": "sentry",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 13.2"
},
{
"name": "hackney",
"manifest": "portal/mix.exs",
"ecosystem": "hex",
"version_constraint": "~> 4.4"
},
{
"name": "github.com/coder/websocket",
"manifest": "runner/go.mod",
"ecosystem": "go",
"version_constraint": "v1.8.15"
},
{
"name": "github.com/creack/pty",
"manifest": "runner/go.mod",
"ecosystem": "go",
"version_constraint": "v1.1.24"
},
{
"name": "golang.org/x/term",
"manifest": "runner/go.mod",
"ecosystem": "go",
"version_constraint": "v0.44.0"
},
{
"name": "rsc.io/qr",
"manifest": "runner/go.mod",
"ecosystem": "go",
"version_constraint": "v0.2.0"
}
],
"all_dependencies": {
"error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"source": null,
"packages": [],
"collected": false,
"truncated": false,
"total_count": null,
"direct_count": null,
"indirect_count": null
}
},
"maintainership": {
"issues": {
"open_prs": 0,
"merged_prs": 2,
"open_issues": 0,
"closed_ratio": null,
"closed_issues": 0,
"closed_unmerged_prs": 8
},
"bus_factor": 1,
"bot_contributors": 1,
"top_contributors": [
{
"type": "User",
"login": "AndrewDryga",
"commits": 269,
"avatar_url": "https://avatars.githubusercontent.com/u/1877644?v=4"
}
],
"contributors_sampled": 1,
"top_contributor_share": 1
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"ci.yml",
"deploy-check.yml",
"promote-image.yml",
"release.yml",
"selfhost-check.yml"
],
"has_docs_dir": false,
"linter_configs": [],
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"go.sum",
"mix.lock",
"package-lock.json"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 9,
"reason": "binaries present in source code",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 3,
"reason": "branch protection is not maximal on development and all release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": null,
"reason": "no pull request found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 0,
"reason": "Found 0/30 approved changesets -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 10,
"reason": "project has 3 contributing companies or organizations -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 10,
"reason": "update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 10,
"reason": "project is fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 10,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 0,
"reason": "project was created within the last 90 days. Please review its contents carefully",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": 10,
"reason": "packaging workflow detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 7,
"reason": "dependency not pinned by hash detected -- score normalized to 7",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 0,
"reason": "no SAST tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 10,
"reason": "security policy file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": 0,
"reason": "Project has not signed or included provenance with any releases.",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 10,
"reason": "GitHub workflow tokens follow principle of least privilege",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 7,
"reason": "3 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "f27b76b8d27761c812e25db45c3fb8de48cc9a3f",
"ran_at": "2026-07-28T07:52:02Z",
"aggregate_score": 6.1,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": true,
"has_dependabot_config": true
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-10T05:07:18Z",
"oldest_open_prs": [],
"last_merged_pr_at": "2026-06-21T22:07:05Z",
"ci_last_conclusion": "FAILURE",
"oldest_open_issues": []
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/AndrewDryga/onlytty",
"host": "github.com",
"name": "onlytty",
"owner": "AndrewDryga"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": null,
"notes": [],
"value": 52,
"inputs": {
"security": 61,
"vitality": 66,
"community": 24,
"governance": 46,
"engineering": 62
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "moderate",
"name": "Vitality",
"value": 66,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "moderate",
"name": "Development activity",
"note": null,
"notes": [],
"value": 50,
"inputs": {
"commits_last_year": 271,
"human_commit_share": 0.98,
"days_since_last_push": 18,
"active_weeks_last_year": 4
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 18 days ago",
"points": 28.8,
"status": "partial",
"details": [
{
"code": "push_recency",
"params": {
"days": 18
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "4/52 weeks with commits",
"points": 2.8,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 4
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "271 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 271
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "project was created within the last 90 days. Please review its contents carefully",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"releases_count": 17,
"latest_release_tag": "v0.4.10",
"releases_from_tags": false,
"days_since_latest_release": 18,
"mean_days_between_releases": 1.7
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "17 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 17
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 18 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 18
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~1.7 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 1.7
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "Project has not signed or included provenance with any releases.",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "unverified",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": "repository_too_young",
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": null,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "maintenance record not established from the collected data",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_unverified",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "critical",
"name": "Community & Adoption",
"value": 24,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 1,
"inputs": {
"forks": 0,
"stars": 2,
"watchers": 0,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "2 stars",
"points": 0,
"status": "missed",
"details": [
{
"code": "stars",
"params": {
"count": 2
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "0 forks",
"points": 0,
"status": "missed",
"details": [
{
"code": "forks",
"params": {
"count": 0
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "0 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 0
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "moderate",
"name": "Community health",
"note": null,
"notes": [],
"value": 50,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": false,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "recognized license (MIT)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "MIT"
}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.3
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "at_risk",
"name": "Sustainability & Governance",
"value": 46,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 20,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 1,
"top_contributor_share": 1
},
"components": [
{
"key": "bus_factor",
"name": "Bus factor",
"detail": "1 contributor(s) cover half of all commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "bus_factor",
"params": {
"count": 1
}
}
],
"max_points": 54
},
{
"key": "commit_distribution",
"name": "Commit distribution",
"detail": "top contributor authored 100% of commits",
"points": 0,
"status": "missed",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 100
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "1 contributors",
"points": 1.4,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 1
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 3 contributing companies or organizations -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "critical",
"name": "Issue & PR responsiveness",
"note": "Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"issue_resolution"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 14,
"inputs": {
"merged_prs": 2,
"open_issues": 0,
"closed_issues": 0,
"issue_closed_ratio": null,
"closed_unmerged_prs": 8
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "no issues or no data",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_issues_or_data",
"params": {}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "2/10 decided PRs merged",
"points": 7.7,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 2,
"decided": 10
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 0/30 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "moderate",
"name": "Ownership & stewardship",
"note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"verified_domain"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 65,
"inputs": {
"followers": 213,
"owner_type": "User",
"is_verified": null,
"owner_login": "AndrewDryga",
"public_repos": 89,
"account_age_days": 5149
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "personal (user) account",
"points": 10,
"status": "partial",
"details": [
{
"code": "owner_personal",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": "not applicable to user accounts",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_applicable_to_user_accounts",
"params": {}
}
],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "213 followers of AndrewDryga",
"points": 16.8,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 213,
"login": "AndrewDryga"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "89 public repos, account ~14 yr old",
"points": 25,
"status": "met",
"details": [
{
"code": "public_repos",
"params": {
"count": 89
}
},
{
"code": "account_age_years",
"params": {
"years": 14
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"github.com/AndrewDryga/onlytty"
],
"ecosystems": "go",
"any_deprecated": false,
"min_days_since_publish": 18
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on go",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "go"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 18 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 18
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "20 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 20
}
}
],
"max_points": 20
},
{
"key": "not_deprecated",
"name": "Not deprecated",
"detail": "active, not deprecated or yanked",
"points": 20,
"status": "met",
"details": [
{
"code": "package_not_deprecated",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
},
{
"key": "engineering",
"band": "moderate",
"name": "Engineering Quality",
"value": 62,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "moderate",
"name": "Engineering practices",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: CI-Tests. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_ci_tests"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 60,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "5 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 5
}
}
],
"max_points": 24
},
{
"key": "tests_present",
"name": "Tests present",
"detail": null,
"points": 24,
"status": "met",
"details": [],
"max_points": 24
},
{
"key": "linter_config",
"name": "Linter config",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 16
},
{
"key": "pre_commit_hooks",
"name": "Pre-commit hooks",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 9.6
},
{
"key": "editorconfig",
"name": ".editorconfig",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "no pull request found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "moderate",
"name": "Documentation",
"note": null,
"notes": [],
"value": 65,
"inputs": {
"topics": [
"elixir",
"go",
"shell",
"terminal",
"terraform",
"tty",
"remote-access",
"remote-control",
"remote-execution",
"self-hosted"
],
"has_wiki": false,
"homepage": "https://onlytty.com",
"has_readme": true,
"has_docs_dir": false,
"has_description": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": "https://onlytty.com",
"points": 15,
"status": "met",
"details": [],
"max_points": 15
},
{
"key": "repository_description",
"name": "Repository description",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": "10 topics",
"points": 10,
"status": "met",
"details": [
{
"code": "topics_count",
"params": {
"count": 10
}
}
],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "moderate",
"name": "Security",
"value": 61,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "moderate",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): CI-Tests. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"ci_tests"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 61,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 17,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 1,
"scorecard_aggregate": 6.1
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "binaries present in source code",
"points": 6.8,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection is not maximal on development and all release branches",
"points": 2.2,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "no pull request found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 0/30 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 3 contributing companies or organizations -- score normalized to 10",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "update tool detected",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is fuzzed",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "project was created within the last 90 days. Please review its contents carefully",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 7",
"points": 3.5,
"status": "partial",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "no SAST tool detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "Project has not signed or included provenance with any releases.",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "GitHub workflow tokens follow principle of least privilege",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "3 existing vulnerabilities detected",
"points": 5.2,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "high_risk_jurisdiction_exposure",
"band": "excellent",
"name": "High-Risk Jurisdiction Exposure",
"note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
"notes": [
{
"code": "jurisdiction_evidence_limits",
"params": {}
}
],
"value": 100,
"inputs": {
"meaning": "self-published location evidence; not nationality or citizenship",
"red_flag": false,
"exposures": [],
"policy_countries": [
"Russia",
"Iran",
"North Korea"
],
"review_only_matches": 0,
"assessed_self_published_locations": 3
},
"components": [
{
"key": "policy_exposure_multiplier",
"name": "Policy exposure multiplier",
"detail": "no confirmed policy-scope location match",
"points": 100,
"status": "met",
"details": [
{
"code": "jurisdiction_no_match",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "good",
"name": "AI Readiness",
"value": 75,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "excellent",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 85,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 0.969,
"agent_instruction_files": [
"AGENTS.md",
"CLAUDE.md",
"GEMINI.md"
],
"agent_instruction_max_bytes": 5586
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "AGENTS.md, CLAUDE.md, GEMINI.md",
"points": 45,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "AGENTS.md, CLAUDE.md, GEMINI.md"
}
}
],
"max_points": 45
},
{
"key": "machine_readable_docs_llms_txt",
"name": "Machine-readable docs (llms.txt)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "legible_commit_history",
"name": "Legible commit history",
"detail": "95 of 98 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 95,
"sampled": 98
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "good",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 75,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"go.sum",
"mix.lock",
"package-lock.json"
],
"has_dockerfile": true,
"typed_language": false,
"bootstrap_files": [
"Makefile"
],
"has_devcontainer": false,
"has_linter_config": false,
"typecheck_configs": [],
"agent_commit_share": 0.82,
"toolchain_manifests": [
"portal/mix.exs",
"runner/go.mod"
],
"dependency_bot_commit_share": 0.02
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": "Makefile",
"points": 18,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "Makefile"
}
}
],
"max_points": 18
},
{
"key": "automated_tests",
"name": "Automated tests",
"detail": null,
"points": 22,
"status": "met",
"details": [],
"max_points": 22
},
{
"key": "lint_format_config",
"name": "Lint / format config",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "Dockerfile, lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "Dockerfile, lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "82 of the last 100 commits agent-authored or agent-credited",
"points": 10,
"status": "met",
"details": [
{
"code": "agent_authored_commits",
"params": {
"count": 82,
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "2 of the last 100 commits are automated dependency updates",
"points": 8,
"status": "met",
"details": [
{
"code": "dependency_bot_commits",
"params": {
"count": 2,
"sampled": 100
}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 7",
"points": 7,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "moderate",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 55,
"inputs": {
"primary_language": "Elixir",
"largest_source_bytes": 55861,
"source_files_sampled": 80,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "Elixir without a type-check config",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_typecheck_config_language",
"params": {
"language": "Elixir"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/80 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 80,
"oversized": 0
}
}
],
"max_points": 55
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [
"Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
"Could not fetch hex package 'onlytty' from its registry",
"GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
],
"report_type": "repository",
"generated_at": "2026-07-28T07:52:08.485520Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/a/AndrewDryga/onlytty.svg",
"full_name": "AndrewDryga/onlytty",
"license_state": "standard",
"license_spdx": "MIT"
}