Informe JSON sin procesar legible por máquina
{
"data": {
"repo": {
"topics": [],
"is_fork": false,
"size_kb": 14018,
"has_wiki": true,
"homepage": null,
"languages": {
"Go": 251223,
"HTML": 10875,
"Shell": 3111,
"Python": 4709
},
"pushed_at": "2026-06-18T09:43:03Z",
"created_at": "2026-04-27T17:48:55Z",
"owner_type": "User",
"updated_at": "2026-06-18T09:42:58Z",
"description": "A small TUI for lofi internet radio. Streams, YouTube live, ambient sounds",
"is_archived": false,
"is_disabled": false,
"license_spdx": "MIT",
"default_branch": "main",
"license_spdx_raw": "MIT",
"primary_language": "Go",
"significant_languages": [
"Go"
]
},
"owner": {
"blog": null,
"name": null,
"type": "User",
"login": "iRootPro",
"company": null,
"location": null,
"followers": 0,
"avatar_url": "https://avatars.githubusercontent.com/u/19166457?v=4",
"created_at": "2016-05-03T09:54:29Z",
"is_verified": null,
"public_repos": 81,
"account_age_days": 3734
},
"license": {
"state": "standard",
"spdx_id": "MIT",
"raw_spdx": "MIT",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v0.1.12",
"kind": "patch",
"published_at": "2026-06-18T09:43:49Z"
},
{
"tag": "v0.1.11",
"kind": "patch",
"published_at": "2026-05-20T11:09:41Z"
},
{
"tag": "v0.1.10",
"kind": "patch",
"published_at": "2026-05-20T10:09:55Z"
},
{
"tag": "v0.1.9",
"kind": "patch",
"published_at": "2026-05-15T06:08:59Z"
},
{
"tag": "v0.1.8",
"kind": "patch",
"published_at": "2026-05-13T11:14:42Z"
},
{
"tag": "v0.1.7",
"kind": "patch",
"published_at": "2026-05-13T11:00:27Z"
},
{
"tag": "v0.1.6",
"kind": "patch",
"published_at": "2026-05-13T10:02:43Z"
},
{
"tag": "v0.1.5",
"kind": "patch",
"published_at": "2026-05-13T08:36:08Z"
},
{
"tag": "v0.1.4",
"kind": "patch",
"published_at": "2026-05-12T13:16:38Z"
},
{
"tag": "v0.1.3",
"kind": "patch",
"published_at": "2026-05-03T19:00:56Z"
},
{
"tag": "v0.1.2",
"kind": "patch",
"published_at": "2026-05-02T19:23:30Z"
},
{
"tag": "v0.1.1",
"kind": "patch",
"published_at": "2026-04-27T18:13:01Z"
},
{
"tag": "v0.1.0",
"kind": "minor",
"published_at": "2026-04-27T17:53:54Z"
}
],
"recent_commits": [
{
"oid": "9a340a77e7668a24327a26d62771287328ed94ac",
"body": null,
"is_bot": false,
"headline": "fix: persist theme selection immediately",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-06-18T09:42:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7f80415bf39267f0b1a96495f99720361c4086df",
"body": null,
"is_bot": false,
"headline": "fix: cap initial buffer for live radio",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-20T11:09:16Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "956fb3d3c7c32c59e619abf2f66e1746e90c3c22",
"body": null,
"is_bot": false,
"headline": "feat: add network buffer settings",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-20T10:09:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2df9f8443747e1ebe9dfcb7e6e855ce8df11e913",
"body": null,
"is_bot": false,
"headline": "fix: reconnect stalled radio streams",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-15T06:07:56Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4ce486b4f1f6f10b0f6fed5d1a98ece2ba0dd20a",
"body": null,
"is_bot": false,
"headline": "feat: add light themes",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-13T11:14:17Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "da085b7e023a027f8fc2632ee0d6e7ebcc1bc5f7",
"body": null,
"is_bot": false,
"headline": "feat: add theme picker",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-13T10:59:50Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9813ed6b6c31407eb55e3ad1a0deefec62574c96",
"body": null,
"is_bot": false,
"headline": "feat: add station sharing",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-13T10:01:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "73dce8a64bd8dd1b4148a7a86aa6d5508b3c5953",
"body": null,
"is_bot": false,
"headline": "audio: support macOS play pause media key",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-13T08:35:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c76ce91cde65d6bc6de28ce78db786292c36515d",
"body": null,
"is_bot": false,
"headline": "fix: isolate mpv startup from user config",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-12T12:09:26Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "62692c179cddb7485a6beda2ee1f020be66a60af",
"body": null,
"is_bot": false,
"headline": "Update README demo GIF",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-04T11:22:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "55d8a0820d7de6edccd84232f227a288b54bf45f",
"body": null,
"is_bot": false,
"headline": "docs: add PNG demo preview",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-03T19:16:06Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ded0f0d33ed20e64731350a6a28ce7b9e9f3dc72",
"body": null,
"is_bot": false,
"headline": "fix: keep mpv IPC responsive under event bursts",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-03T18:59:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6ff0f7f31cc4432ce83fb4afcf2e76f276403c52",
"body": null,
"is_bot": false,
"headline": "docs: update pinned install version",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-02T19:22:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "770d734aab03838c3e192d81a1dab7168112ad19",
"body": null,
"is_bot": false,
"headline": "fix: polish TUI layout and modal overlays",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-05-02T19:21:47Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d6161dd8a0db0b5f4b6accf52fcf37c344e01d75",
"body": null,
"is_bot": false,
"headline": "docs: refresh demo gif",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T19:26:47Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0ec712733b0acdd1f984e9661565ffc9a5aa67b4",
"body": "Reorders the install section so Homebrew (which auto-installs mpv\nand yt-dlp) is clearly the path of least resistance. Adds a note to\nthe one-line installer pointing at the runtime-deps table since\nnon-brew installs need mpv installed separately. Lifts the supported\nplatforms line to the top of the section.",
"is_bot": false,
"headline": "docs: highlight Homebrew as the recommended install path",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T18:17:41Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8bd081d803f39b1eec320fcb114376ac41aa33c2",
"body": null,
"is_bot": false,
"headline": "chore: ignore local /sounds/ scratch dir",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T18:12:28Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ed55394d704d52493ff3e892f60dff86e1be0a52",
"body": "Releases are now cut locally with `goreleaser release --clean` rather\nthan from a CI workflow, mirroring the iRootPro/rdr setup. Goreleaser\nuploads binaries to GitHub Releases and pushes the formula to\niRootPro/homebrew-tap in one shot, using the local gh-cli token —\nno PAT secret needed in the repo\n[…]\nl/bin\n (override with INSTALL_DIR=...). Hints when mpv is missing.\n- README.md / README.ru.md: install section adds Homebrew tap and\n one-line installer paths; release notes describe the local flow.",
"is_bot": false,
"headline": "chore(release): switch to local release flow with Homebrew tap",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T18:10:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9764af8574cd6d05f7097032c13964d0f9258b4d",
"body": "- README.md: language switcher in the header pointing at README.ru.md;\n Features list mentions the friendly-first-run behavior; runtime\n dependencies section now distinguishes the mpv hard-fail with the\n styled card from the yt-dlp soft warning that keeps the rest of\n the app playable.\n- README.ru.md: full Russian translation of the README, same\n structure and links so the cross-references stay symmetric.",
"is_bot": false,
"headline": "docs: add Russian README and document graceful dependency handling",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T17:47:13Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "53173c5b348a40d2f26d80e2c88c2ee297238809",
"body": "mpv missing on $PATH no longer crashes with a raw error. Instead a\nstyled `lofi.player can't start` card is rendered to stderr with\nplatform-specific install commands (brew / apt / pacman / dnf), and\nthe process exits 1.\n\nyt-dlp missing degrades to a soft warning rather than refusing to\nstart when t\n[…]\nause replaces the playCmd with an\n error toast that names the missing tool.\n- The station list paints YouTube rows in muted hint color and\n appends an `unavailable` badge so the user sees the cause.",
"is_bot": false,
"headline": "feat: friendly handling of missing runtime dependencies",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T17:46:59Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f410efa5076ef3612edfb75f8875c4d0d91e6450",
"body": "Rewrites the README around the public-release shape:\n\n- Badges (CI, latest release, Go version, MIT)\n- Centered demo GIF (demo/lofi-player.gif from the vhs tape)\n- Features bullet list (radio, YouTube, ambient mixer, themes,\n mini mode, statusline, persistence, edit/add/delete stations,\n stream in\n[…]\nnd auto-detect note\n- Themes / mini mode / tmux statusline sections\n- Credits for CC0 ambient samples (rain, fire, white noise, cafe,\n thunder) with original freesound.org links\n- MIT license callout",
"is_bot": false,
"headline": "docs: rewrite README with badges, demo, full keybindings",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T12:01:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5318e95fc81a0ebf4a88d7c7ce27d22f340e4751",
"body": "UX polish:\n- Animation tick chains hoisted above modal dispatch so the logo\n shimmer and live-pulse keep ticking when mixer/add-station modals\n are open (the modal updaters only know about KeyMsg and were\n silently swallowing tea.Tick chains, killing them forever).\n- Full-help card switched from \n[…]\nens a small `delete <name>?` confirmation modal centered in\n the frame; `y/enter` commits, `n/esc` cancels. Deleting the\n currently-playing station pauses playback and clears the\n now-playing card.",
"is_bot": false,
"headline": "feat(tui): UX polish, stream info, edit/delete stations",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T12:01:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "95e7d8408caa7d24f3dcc7782522957c2bdcce1e",
"body": "Adds the bits needed to publish the project to GitHub:\n\n- LICENSE (MIT, 2026 Alexander Neupokoev)\n- .goreleaser.yaml — builds linux/{amd64,arm64} + darwin/{amd64,arm64},\n archives include README and LICENSE, checksums, ldflags inject\n main.version\n- .github/workflows/ci.yml — vet + test + build on\n[…]\ne — /dist/ for goreleaser output\n- main.go — `--version`/`-v` flag, ldflags-overridable `version`\n variable, ShowStreamInfo wiring through state\n- demo/ — vhs tape and pre-rendered GIF for the README",
"is_bot": false,
"headline": "chore(release): packaging artifacts and version flag",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-27T12:00:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "cc8d1c398625c02b0f4efc99b9a4e497fc41c67d",
"body": "Two new CC0 ambient channels appended to the registry, both sourced\nfrom freesound.org and processed via ffmpeg into 4-minute Opus loops:\n - cafe.opus ← freesound 32910 by ToddBradley (CC0)\n - thunder.opus ← freesound 717890 by TRP (CC0)\n\nTotal ambient set: rain, fire, white_noise, cafe, thunde\n[…]\nover the expanded 5-channel registry — channel order\nwalk-through, label/icon metadata, and indicator rendering all\nparametrized off ChannelIDs() rather than hard-coded against the\noriginal 3 entries.",
"is_bot": false,
"headline": "Add cafe and thunder ambient channels; restyle mixer icons",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T18:41:29Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "316f5be4f4cdec7b75b84f42a7f13747ebc5b071",
"body": "Adds the shimmering \"lofi\" ASCII logo on the right side of the\nmain view, vertically centered against the body height. Resolved\ntrivial conflicts where ambient-mixer and the logo each appended\nnew tick messages, tea.Cmd factories, and view helpers — kept\nboth. Bumped logo_test.go's NewModel call to match the new mixer\nparameter introduced on main.",
"is_bot": false,
"headline": "Merge branch 'feat/equalizer'",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T18:18:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "043e1b784dd2ffb6327de87a5a0c571d17bb01ee",
"body": "Trim the spacer between the ♪ prefix and the L from 2 cells to 1\nso the music note reads as part of the word rather than floating\nbeside it. Swap the i's middle-dot (•) for a black-circle (●) so\nthe i carries comparable visual weight to the heavy-stroked L, O,\nand F instead of trailing off into a pin-prick.",
"is_bot": false,
"headline": "TUI: tighten logo prefix gap and beef up the i's dot",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T18:10:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "629b6aa0ee69d043db644d6ec693ebf08e206041",
"body": "Move the shimmering logo from the top of the right column to its\nvertical middle. JoinHorizontal with lipgloss.Center pads the logo\nabove and below to match the full body height (now-playing card +\nstations list), so the right side reads as balanced rather than\ntop-heavy with empty space below the original 3-row logo strip.",
"is_bot": false,
"headline": "TUI: vertically center logo against body height",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T18:07:25Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0f205a09b61ac6d4d0bb10e69c146e915803bf10",
"body": "Swap the light box-drawing glyphs (│─╭╮╰╯├) for their heavy\ncounterparts (┃━┏┓┗┛┣) so the letters carry more visual weight and\nthe shimmer wave reads more strongly across them. Bump the i's dot\nfrom · (middle dot) to • (bullet) to match the heavier strokes.",
"is_bot": false,
"headline": "TUI: thicken logo strokes with heavy box-drawing",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T18:03:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1a86fd53eb134fbacbca58cfab7b3a2a825bc9b9",
"body": "Dot the \"i\" with a middle-dot glyph so the rightmost letter reads\nas a real lowercase \"i\" rather than a bare divider. Echo the music\nnote from the frame's top-border title on the middle row of the\nlogo, so the brand element lives in both the frame chrome and the\nlarger panel — same glyph, two scales.",
"is_bot": false,
"headline": "TUI: refine logo with dot-i and ♪ prefix",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T17:57:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "29ae60ffe5b8486772a026ba959cb271a6cabdbb",
"body": "The vertical-bar equalizer felt too \"club\" for a lofi player; swap\nit for a small static \"lofi\" logo built from rounded box-drawing\ncharacters. A soft shimmer crest sweeps across the logo at 150 ms\nper cell, brightening cells from Muted → Secondary → Primary as the\nwave passes, with a brief dim \"res\n[…]\nanimation reads as a calm breath rather than a continuous spinner.\n\nShimmer freezes on pause and the logo collapses entirely when no\nstation is selected, matching the previous equalizer's state rules.",
"is_bot": false,
"headline": "TUI: replace equalizer with shimmering ASCII logo",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T17:47:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a9b7043ff40ce1518566481d00c34914d478cd6d",
"body": "Adds the ambient mixer feature (Phase 4c from plans/lofi-player-plan.md):\n3-channel mixer (rain/fire/white_noise) layered over the main station,\nopened with 'x', state persisted via debounced save, embedded CC0 loops\nfrom freesound.org. See plans/2026-04-25-ambient-mixer-design.md for the\ndesign and plans/2026-04-25-ambient-mixer-plan.md for the implementation\nplan.",
"is_bot": false,
"headline": "Merge branch 'ambient-mixer'",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T17:44:58Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "10104624fbcf9f20b1730151331a8ea73399f55a",
"body": "- rain.opus ← freesound 525046 by speakwithanimals (CC0)\n- fire.opus ← freesound 760474 by True_Killian (CC0)\n- white_noise.opus ← freesound 132275 by assett1 (CC0)\n\nAll three are public-domain field recordings. Encoded to Opus 64 kbps\nstereo with 2/3-second fade in/out and EBU R128 loudness norma\n[…]\nization\nto -23 LUFS. README documents the regen recipe so future contributors\ncan reproduce or swap the embeds without spelunking git history.\n\nTotal embed footprint ~4.3 MB (was ~4.2 MB synthesized).",
"is_bot": false,
"headline": "audio: replace synthesized loops with CC0 freesound recordings",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T17:42:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3e790501edf5e6f26d17b6942780892e8f25560a",
"body": "The previous placeholders were 1-3 seconds of literal silence — fine for\nunit tests, useless for verifying the audio pipeline by ear. Swap them\nfor 4-minute Opus loops generated via ffmpeg's anoisesrc:\n\n- rain.opus — pink noise filtered (highpass 500 / lowpass 7000) with\n light tremolo to feel lik\n[…]\nd recordings — fine for\nshipping a first release (similar to what mynoise.net and Brain.fm\nship), with real CC-BY recordings as a follow-up before v1.0. Recipe\nand licensing live in the assets README.",
"is_bot": false,
"headline": "audio: replace silent placeholders with synthesized noise textures",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-26T17:24:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "79731cbaae5d5fc7171fca2efc8652015921a67b",
"body": "- Init the mixer at startup (non-fatal on failure: the modal still\n opens, channels show as 'unavailable').\n- Restore last-session ambient volumes from state.json before the\n Bubble Tea program starts so playback resumes immediately.\n- Plumb a SaveAmbient closure that load-modify-saves the state file\n on each debounced tick. Errors flow back to the model as toasts.\n- On clean shutdown, snapshot Volumes() into the final state.Save.",
"is_bot": false,
"headline": "main: wire ambient mixer init, restore, and persistence",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T20:05:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "11bafeaecd54d0f0bd6b1fe72c374f1791e4f517",
"body": "SaveAmbient callback returns error now; the debounce handler renders an\nerror toast on failure, matching how config.Save errors flow through\nupdateAddStation. Tighten the nil-callback test (toast remains nil, seq\nunchanged) and add a wiring assertion that mixer keys return a non-nil\ntea.Cmd.",
"is_bot": false,
"headline": "tui: surface ambient save errors as toast (matches addform pattern)",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T20:05:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "36a388d9797a462fb74d02bc7af706fc3662c1ef",
"body": "Sixteen vertical bars sit right of the now-playing block, animated\non a 120 ms tick via per-bar sin-driven phases. Bars freeze on pause,\nquiet to a small ripple while loading, and disappear when no station\nis selected or when the terminal is too narrow. The animation has no\naudio data behind it — the mpv JSON-IPC backend never surfaces PCM —\nso phases are seeded deterministically and snapshot tests are stable.",
"is_bot": false,
"headline": "TUI: animate decorative equalizer next to now-playing card",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T20:02:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "11f6689c402e66eaf4fcb8af5d961e96c6752b69",
"body": null,
"is_bot": false,
"headline": "tui: debounce ambient state save by 500ms",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T20:01:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6d4e49190d1939ee46697af39c89fa9d66740307",
"body": "Adds a compact 'mid-dot + icons' indicator to the station line in\nrenderNowPlaying. Pulls active channel ids from the audio mixer in\ncanonical order; hides entirely when nothing is active.",
"is_bot": false,
"headline": "tui: show active ambient channels next to station",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:57:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "fd734c7ed39b7bf1c1da211f5e4c800d0045f062",
"body": null,
"is_bot": false,
"headline": "tui: regression test for inert global keys in mixer modal",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:56:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "566936b46f6791416cdd2d578355dbfc3f976f63",
"body": "Centered rounded card with 3 channel rows (icon, label, bar, value),\ntitle and hint bar. Selected row highlighted; muted (volume==0) rows\ndim; disabled rows show 'unavailable'.",
"is_bot": false,
"headline": "tui: render ambient mixer modal card",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:55:56Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "14a1f92c2e9ccc98a80ab1bf5502afef9a309def",
"body": "Wire the modal's nav and volume keys: j/k pick a channel, h/l adjust by\n±5, H/L by ±25, 0 mutes the active channel, 1 maxes it. Volume changes\ngo straight to the audio mixer's SetVolume; the modal model just tracks\nwhich row is selected.\n\nAdds audio.SetCacheDirForTest so cross-package tests can redirect the\nambient cache root without depending on $XDG_CACHE_HOME (which is\nignored by os.UserCacheDir on darwin).",
"is_bot": false,
"headline": "tui: ambient mixer keybindings (jk/hl/HL/0/1)",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:54:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ea70f41d6acd8a8fb591da9a936c131a340cfdd4",
"body": null,
"is_bot": false,
"headline": "tui: introduce mixerModel.Selected for modal state",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:53:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "951267d23994d9959ba10d2555196a6d12fde48d",
"body": "- Adds modeMixer to viewMode plus a mixer field on Model and a\n mixerModel struct in mixer.go (selection state only; volumes\n live on audio.AmbientMixer).\n- NewModel now takes *audio.AmbientMixer; main.go constructs one\n via audio.NewAmbientMixer (Init/Close wired up in Task 15).\n- Update routes input to updateMixer while modeMixer is active;\n for now it only handles esc/x to close, plus q/ctrl+c. Full\n navigation/volume keys land in Task 10.",
"is_bot": false,
"headline": "tui: add modeMixer with open/close transitions",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:52:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8dd6047b8e2ce56639e5f557e0888dace96b8a9a",
"body": null,
"is_bot": false,
"headline": "tui: bind 'x' to open ambient mixer",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:49:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6946f08f5084328faa04ca313695954849cab546",
"body": null,
"is_bot": false,
"headline": "state: persist ambient channel volumes",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:48:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f71b2172b8cd268cbd805acc639389c43c022439",
"body": null,
"is_bot": false,
"headline": "audio: ambient mixer Volumes and ActiveIDs snapshots",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:48:04Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6313665eecc4235a6d4faad59747dc7d3df9a75e",
"body": null,
"is_bot": false,
"headline": "audio: ambient mixer set-volume with pause-on-zero",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:45:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b863ecf58c83f81f3ab18cc2e939cd1c14300e22",
"body": null,
"is_bot": false,
"headline": "audio: close orphaned mpv on Init failure; document placeholder regen",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:43:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8ebff4b04f19e14fe30b9be5548a24120a569db8",
"body": "scripts/fetch-radiopotok.py scrapes chillout/jazz/classical/trance\ncategories from radiopotok.ru, deduplicates by URL+name, prefers\nmp3 > aac > other > m3u8, and emits configs/radiopotok.yaml.\nYouTube-only stations are converted to youtube-kind entries.\n\nOutput schema matches internal/config.Station so users can copy\nentries straight into ~/.config/lofi-player/config.yaml. Defaults\nin code are unchanged.",
"is_bot": false,
"headline": "configs: bundle ~200-station radiopotok.ru preset",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:42:33Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b62baded926d4c9369669be2d53ea05fb73ee869",
"body": "Adds ambientPlayer wrapping per-channel mpv subprocess (loop=inf, idle=no,\nvolume=0, pause=yes). Init spawns one per channel; failure marks the\nchannel disabled and continues. Close terminates all subprocesses in\nparallel.\n\nTwo pragmatic fixes alongside the new code:\n- ambient_player cleanup no long\n[…]\ne text placeholders in ambient_assets/ with valid\n silent Opus loops (1/2/3-second durations for distinct SHA-256). mpv\n can decode these, so spawn tests don't depend on its decode-failure\n timing.",
"is_bot": false,
"headline": "audio: spawn mpv per ambient channel on init",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:39:00Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "05dc2125c00e55486076ba9cb359c8640ba26100",
"body": null,
"is_bot": false,
"headline": "audio: fsync materialize and wrap error contexts",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:24:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8cdf6e7ebf33aa4f9fe57de41f15a1f179b1a6a2",
"body": null,
"is_bot": false,
"headline": "audio: embed ambient loop files and extract on init",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:19:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9a43b86bea1a95d5a967be92e4892e638ac9945c",
"body": null,
"is_bot": false,
"headline": "audio: scaffold ambient channel registry",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:14:17Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1435e270f6a8bafb1443cff1cce14f3aaa395bae",
"body": null,
"is_bot": false,
"headline": "plans: add ambient mixer implementation plan",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:10:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "410b3125ee214dde3acc0eb4ca4b2c8b1984014e",
"body": null,
"is_bot": false,
"headline": "gitignore: add .worktrees/ for isolated feature workspaces",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:03:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "27e0a27e3f3133bba975c307187fcba1c6fd56ce",
"body": null,
"is_bot": false,
"headline": "plans: add ambient mixer v1 design",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T19:00:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "348859348cb1fa307728576fa3848136b548f303",
"body": "Two cells of whitespace between an icon and its label looked like a\ngap; one cell reads as a properly-paired tag. Apply to:\n\n - title bar logo: \"♪ lofi.player\" → \"♪ lofi.player\"\n - stations section header: \" stations\" → \" stations\"\n\nVisually balances against the volume widget in the title bar (which\nalready used a single space) and tightens the leading icons against\ntheir text without losing breathing room.",
"is_bot": false,
"headline": "TUI: tighten icon-to-label spacing in logo and stations header",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:38:17Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "63eb623dd5e2ff43ffe4abe6906755180409f826",
"body": "The previous Warning-yellow \"?\" stood out as a stranger in the\notherwise muted bottom border. Use AppTitle (Primary purple)\ninstead, matching the logo glyph at top-left and the volume icon\nat top-right. The three \"interactive entry points\" are now\nvisually consistent — same color says \"these belong together\".\n\n\"help\" word stays in HelpDesc muted so the hint as a whole still\nreads quietly against the border.",
"is_bot": false,
"headline": "TUI: paint \"?\" hint in the brand Primary color",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:35:25Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e6d5eda58aed4c2f8318afb24da9174d7f075105",
"body": "The persistent row of bindings under the stations list was visual\nchatter for users who already know the keys. Replace it with a\nsingle muted \"? help\" hint embedded in the bottom border (mirroring\nthe volume widget that lives in the top border):\n\n ╭─ ♪ lofi.player ───────── ▰▰▰▰▰▰▱▱▱▱▱▱▱▱ ─╮\n │\n[…]\no — same logic\n - Orphaned helpers gone: KeyMap.ShortHelp, KeyMap.MiniShortHelp,\n Model.renderShortHelp, package-level renderBindings\n - bubbles/key import removed from view.go (no longer needed)",
"is_bot": false,
"headline": "TUI: drop the inline help bar, embed \"? help\" in the bottom border",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:33:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7e501b685fb5ab09199d570fb5067fd31f741bd2",
"body": "Three follow-up tweaks on the volume widget in the frame's title\nbar:\n\n - VolLabel switches from Muted to Primary so the speaker icon\n matches the fill cells in color. Icon + bar now read as one\n unified \"volume control\" rather than icon (decorative) + bar\n (the actual indicator).\n - The \n[…]\nighter in the title bar\n without the components feeling cramped.\n\nVolPercent style stays — it's still used by the --statusline CLI\nmode where the bar isn't shown and the digit is the entire signal.",
"is_bot": false,
"headline": "TUI: tighten volume widget — purple icon, no percent, less padding",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:27:11Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6a077f86ee87bb5a144b33b343392b5a5349f0ce",
"body": null,
"is_bot": false,
"headline": "TUI: drop Bold on volume icon — pointless on the MD glyph",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:24:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3fa77fbe158cffdac713b583d1a32ae62565e0f3",
"body": "Tried the FA volume-up (small-but-with-waves) and Bold-FA variants\nsince the previous MD switch — neither gave the visual weight the\ntitle bar needed. Settle on MD volume_high (U+F057F, \"\"): heavier\nsilhouette, quiet muted gray, sits at proper size in the title\nbar. The lack of sound-wave arcs is a known trade-off the user\naccepts for the visual presence.\n\nBold on the volume label is dropped — pointless on the MD glyph\nwhich renders identically.",
"is_bot": false,
"headline": "TUI: switch volume icon back to MD volume_high silhouette",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:24:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b56ee24bf67f4522dea5326916d71546138c6487",
"body": "The FA volume-up glyph reads as light/thin in the title bar. Apply\nBold to the icon style so terminals with a heavier weight in their\nNerd Font show a thicker speaker; on fonts without that variant the\nfallback is identical to before, no layout break.",
"is_bot": false,
"headline": "TUI: bold the volume icon for a heavier glyph render",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:22:45Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "21051d20c5914d5219acf7e6b2f7f2642aefe795",
"body": "The Unicode 🔊 emoji ended up reading as a color 3D button that broke\nthe flat muted style and sat taller than a single text row. Drop the\nemoji and go back to FA volume-up (U+F028, \"\"). It's small, but\nin-style with the rest of the Nerd Font icons (logo, stations) and\nsits cleanly inline on the title bar.",
"is_bot": false,
"headline": "TUI: revert volume icon to plain FA volume-up",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:21:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9dcc711ede6bd40495fe1d00d8969da3adb4dd48",
"body": "Both Nerd Font candidates were either too small (FA volume_up ),\nunrecognized on the user's font (FA broadcast-tower , rendered as\n\"≡↑\"), or sound-wave-less (MD volume_high ). Drop the Nerd Font\ndependency for this glyph and use 🔊 (U+1F50A SPEAKER WITH THREE\nSOUND WAVES), the standard Unicode emoji\n[…]\no font-dependent fallback. The\nnative emoji color may override the Muted style we apply, which is\nfine for this use: the speaker is meant to read as a recognizable\nicon, not as a quiet section marker.",
"is_bot": false,
"headline": "TUI: use the unicode speaker emoji for the volume icon",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:18:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f78375e86e549c8f29fba51b5354cd35540a19e1",
"body": "FA volume_up () was visually too small for the title bar — it\nread as an afterthought next to the bar. FA broadcast-tower ()\nis heavier in most Nerd Fonts and clearly signals \"broadcasting\nsound\" with the antenna + wave arcs, so the volume widget reads\nwith proper weight at the top-right of the frame.",
"is_bot": false,
"headline": "TUI: bigger volume icon — switch to FA broadcast-tower",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:17:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0a189c8524e1ce1dd12f862e6bcd05299e060d3d",
"body": "Two icon walk-backs after the user clarified what they actually\nmeant:\n\n - Logo glyph reverts from headphones () to the original music\n note (♪). The headphones change was based on a misread of the\n user's request — they were asking about the volume icon, not\n the logo.\n - Volume icon rev\n[…]\nith no sound waves, which read as \"mute\" / no\n audio. FA volume_up has the radiating wave arcs that make the\n speaker visually \"active\" — exactly the \"playing audio\" feel\n the user was after.",
"is_bot": false,
"headline": "TUI: revert logo to music note, switch volume back to FA volume-up",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:16:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f92f4d406085acba438e6ab3ceac178f4f7dab8f",
"body": "The static ♪ note read as \"this is sheet music\" rather than \"this is\nplaying audio\". Headphones (FA U+F025, \"\") put the brand into a\nlistening posture — visually the title bar now signals \"you're\nhearing something through this app\" instead of \"music notation\".",
"is_bot": false,
"headline": "TUI: swap logo glyph from music note to headphones",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:14:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dfd8bb3879df135dfd4bfbf1faf02eb5738d643b",
"body": "Two related polish points: the spring-animated volume bar made the\ntitle bar dance during +/- presses (filler hyphens shifted left and\nright as the percent string changed width 80% → 100%), and the user\npreferred discrete 5%-jumps over the smooth fill.\n\nDrop the spring animation entirely:\n - intern\n[…]\n.100 range, so the\nfiller \"─\" segments don't jitter when the digit count changes.\n\nrenderVolume reads m.volume directly; the percent and the bar\nnaturally agree because they share the source of truth.",
"is_bot": false,
"headline": "TUI: instant volume changes with fixed-width percent",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:12:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f164acbaa93bd2a404e4925954f34f659af4882b",
"body": "Two fixes for the volume widget in the title bar:\n\n1. The percent digit was reading m.volume (the target) while the bar\n filled cells came from m.volumeDisplayed (the spring-animated\n value). On +/- the digit jumped instantly to \"85%\" while the bar\n was still climbing through 80→81→...→85, so \n[…]\nn a void\". Muted gives the empty cells\n enough contrast to read as a quiet placeholder rail, so the\n filled cells look like progress drawing across a track instead of\n appearing out of thin air.",
"is_bot": false,
"headline": "TUI: sync volume percent with the animated bar; brighter empty rail",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:08:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a0246a2bd257e3f23069263b9282d5aa773cb6bc",
"body": "Volume bar + icon + percent now live as the right-side label in the\nframe's top edge, replacing the clock. The body of the app drops the\ndedicated volume row entirely:\n\n ╭─ ♪ lofi.player ───────── ▰▰▰▰▰▰▱▱▱▱▱▱ 85% ─╮\n │ │\n │ ● Lofi Girl 24/7 \n[…]\nsegments.\n\nrenderVolume keeps its previous spring-animation logic (volumeDisplayed\nticks toward volume, see anim.go) — the rendered output now happens to\nland in the title bar rather than its own row.",
"is_bot": false,
"headline": "TUI: move volume widget into the frame's top border",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T18:03:55Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "635f22e77821056c8c51c951d0a4c02291798b93",
"body": "Symmetric labeling: stream-kind rows get \"· stream\" instead of being\nunmarked. Both source types are now visually equal — the user can\nalways tell which entries route through ytdl_hook and which hit mpv\ndirectly, without having to remember which one is the default.\n\nstationKindIcon switches from a YouTube-only conditional to printing\n\"· \" + EffectiveKind() for any non-empty kind (so a future custom\nkind shows up too instead of silently disappearing).",
"is_bot": false,
"headline": "TUI: tag every station with its kind (\"· youtube\" / \"· stream\")",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T17:58:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1f10f32ee578939e84cf2e4b528d131eff2db3e6",
"body": "The \"· yt\" abbreviation was unfamiliar enough to read as noise.\nSpell out \"· youtube\" — extra characters are worth it for the\nzero-ambiguity reading. The rule \"tag the exception, not the\ndefault\" stays: stream (default) entries still get no marker, only\nYouTube-kind rows pick up the muted suffix.",
"is_bot": false,
"headline": "TUI: spell out \"youtube\" instead of the cryptic \"yt\"",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T17:56:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "956789252e7feffb8861559bb94d7b1777975885",
"body": "The Nerd Font youtube codepoint (U+F167) doesn't render reliably —\non the user's terminal it showed up as a generic ◇ diamond, which\ndefeats the whole point of an at-a-glance source indicator.\n\nSwitch to a small textual tag in muted: \"· yt\" appended after the\nstation name on YouTube-kind rows. Strea\n[…]\n stays clean and the rare\nYouTube entry visually stands out without the eye having to parse a\nrss-vs-yt distinction on every line.\n\nRemoves the now-unused iconKindYouTube and iconKindStream constants.",
"is_bot": false,
"headline": "TUI: textual \"· yt\" tag for YouTube stations, drop unreliable glyph",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T17:54:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a6a1b45629a69df6036b631d7866e78f65d57af8",
"body": null,
"is_bot": false,
"headline": "Remove stray view.go.orig left from a patch attempt",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T17:52:04Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "97ab1995d52e9b94f8bc3965c3aa6d09f3525ecf",
"body": "Each station picks up a tiny Nerd Font icon indicating its source:\n (FontAwesome youtube) for Kind=youtube, and (FontAwesome rss)\nfor Kind=stream — direct HTTP/Icecast streams. The glyph is rendered\nin the SectionHeader (Muted) tone, so it sits as a quiet tag next\nto the bold station name without c\n[…]\nname, so a quick visual scan shows which entries route through\n yt-dlp.\n\nstationKindIcon is the only render helper added; it falls back to\nempty string for unknown kinds rather than rendering tofu.",
"is_bot": false,
"headline": "TUI: station-kind glyph next to station names",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T17:51:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "494bac2b3866137ffdb4479c9af7195047c6e73f",
"body": "While a station is playing (loading flag cleared), the live ●\nindicator now alternates between full intensity and dimmed every\n700 ms via the SGR Faint attribute. Calm 1.4 s breath, no color\nchange — reads as \"alive\" rather than \"blinking alarm\".\n\nImplementation:\n - new pulseTickMsg + pulseTick com\n[…]\np (one bool flip + one tea.Tick reschedule)\n\nFaint chosen over per-color RGB math so the pulse adapts to the\ncurrent theme automatically — every theme's StatusLive picks up the\ndim treatment for free.",
"is_bot": false,
"headline": "TUI: soft heartbeat pulse on the live ● indicator",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T14:04:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "70e822c4f03bbe3fca668c3222fb27ff5ffc3153",
"body": "The brand and the clock now ride the top edge of the outer frame,\ntitle-bar style:\n\n ╭─ ♪ lofi.player ────────────────── 16:57 ─╮\n\nThis frees the row inside the frame that the standalone header was\noccupying, makes the app look more like a self-contained desktop\nwindow, and removes the visual repe\n[…]\nline top and bottom gives the content vertical breath.\n\nviewFull and viewMini drop their renderHeader call (the brand has\nmoved to the frame). renderHeader is removed since nothing else\nreferences it.",
"is_bot": false,
"headline": "TUI: title and clock embedded in the frame's top border",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T14:03:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d654df26cabd5d8b10ef4fe8d5f518d02ec1acfa",
"body": "The frame was stretching across the full terminal width. On a 200+\ncell screen the content hugged the left edge with a sea of empty\nspace on the right. Pin the frame to 100 cells and center it with\nlipgloss.PlaceHorizontal — on wide terminals the app reads as a\ncontained panel sitting in the middle \n[…]\ntent starts wrapping awkwardly).\nInner content width passes through as frameWidth - 2 so headers\nand the now-playing track-line truncation budget align inside the\nborders the same way they did before.",
"is_bot": false,
"headline": "TUI: center the app frame at a fixed width on wide terminals",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:56:51Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5fb6f46f2b0af0f6b652626b7048d3e783e9a788",
"body": "Two nested rounded borders (outer \"app\" + inner \"now playing\")\nwere competing for attention. Drop the inner one — the outer frame\nis now the only container on screen, and now-playing reads as the\ntop section inside that frame, given visual weight by the bold\nstation name and the leading ●/◯/spinner \n[…]\no leftPad-prefixed lines.\nThe track-line truncation budget tracks innerWidth (terminal width\nminus leftPad on both sides, capped at nowPlayingMaxWidth) so long\ntitles still get the ellipsis treatment.",
"is_bot": false,
"headline": "TUI: drop the inner now-playing card, keep the outer app frame",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:52:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5eff956b9e357e5905f60d3b8dea72c134dd4bc8",
"body": "The whole UI now lives inside a single rounded card so the layout\nreads as one contained surface instead of a stack of unrelated rows\nfloating in the terminal. Border color is Subtle (one rung quieter\nthan the now-playing card's Muted) so the two borders coexist —\nouter reads as \"app frame\", inner r\n[…]\ntal padding stays at 0 so the existing leftPad inside content\nkeeps doing the indent (no double-indent).\n\nNo changes to behavior or any other rendering logic — the wrap is a\nsingle addition in View().",
"is_bot": false,
"headline": "TUI: wrap the entire app in an outer rounded border",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:48:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "37d80348263b5f68e27e34da96269071ea033c06",
"body": "…ding\n\nThree small follow-ups from the latest screenshot:\n\n- Volume bar widens from 10 cells to 14 cells. The bar was reading\n as a token next to the now-playing card; 14 cells gives 5%-step\n changes a noticeable visual jump and brings the bar closer to the\n card's visual weight.\n- Adds back a bl\n[…]\ncells of breathing room reads as\n intentional layout rather than \"barely indented\". Card MarginLeft\n follows automatically since it derives from len(leftPad).\n\nNo behavior changes; tests stay green.",
"is_bot": false,
"headline": "TUI cosmetic pass: wider volume bar, breathing room, premium left pad…",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:41:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6f3a4db6c5669a513391c7a0ddf1b5963fc18000",
"body": "Five small visual tweaks the user pointed out from the latest screenshot:\n\nThe now-playing card no longer stretches across the full terminal\nwidth. nowPlayingMaxWidth = 80 caps it; on a 200-cell terminal the\ncard now reads as a focused element rather than a stretched panel\nwith one line of content f\n[…]\nons · 5\".\nTrivial information density boost — also balances the leading icon.\n\ngo vet + go test still green; no test changes needed since none of\nthese touched user-visible behavior beyond aesthetics.",
"is_bot": false,
"headline": "TUI cosmetic pass: card width, help labels, cursor color, station count",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:25:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a717e43f7a5d58255b55d576ad9a5807546d71e9",
"body": "The loading flag in the TUI was getting stuck while music was already\nplaying because mpv's pause property doesn't fire a change event when\nloadfile transitions from one playing stream to another (false → false\nisn't a \"change\"). The TUI was waiting for PlaybackStarted that would\nnever arrive.\n\nSwit\n[…]\nner to\nflip back to ●.\n\nThe pause property handler stays as a fallback for genuine\npause/resume transitions where the property does change, so toggling\nspace on the same station still works as before.",
"is_bot": false,
"headline": "audio: emit PlaybackStarted on mpv playback-restart event",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:18:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "62f87746ead8ef3cf8abcb29860b6ebb5e9ab562",
"body": "User decided this added complexity without enough day-to-day value.\nThe git history (commit 91c5d1f) preserves the full implementation;\nthis removes it from the runtime cleanly.\n\nDeleted packages:\n - internal/pomodoro: state machine + Stats type + tests\n - internal/notify: OS desktop notifications\n[…]\n streak +\nlistened mentions.\n\ngo.mod stays the same — no external deps were unique to pomodoro.\nAll tests still pass, no exported audio/config/state/theme/tui API\nbeyond pomodoro pieces was disturbed.",
"is_bot": false,
"headline": "Drop pomodoro / streak / listened-time feature",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:11:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "96e9064f2f4b4effccc572a6bdcb5af5a7ed1387",
"body": "The spinner that lived in the track line (\"buffering metadata\") fired\nwhile audio was already playing — a visible animation during normal\nlistening was noise, not information. Revert that branch back to the\nquiet \"…\" placeholder.\n\nThe status-indicator spinner stays as is: it animates only while\nloading is true (between dispatching playCmd and the first\nPlaybackStarted event from mpv), which is the genuine \"not yet\naudible\" window.",
"is_bot": false,
"headline": "TUI: drop the track-line spinner, keep the status one",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:02:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f5ac3ee7cdadd022906364b6c2582c03203d772d",
"body": "A new \"loading\" flag on Model tracks the gap between dispatching a\nplayCmd (or autoplay on startup) and the first PlaybackStarted event\nmpv sends back. While loading is true, the status indicator — both\nin the now-playing card and beside the playing-station row in the\nlist — renders the existing bra\n[…]\nrefreshes spinner.Style so the spinner color tracks the active theme.\nTestView_ShowsPlayingMarker updated to drive the model through the\nloading → playing transition explicitly via PlaybackStartedMsg.",
"is_bot": false,
"headline": "TUI: spinner in status indicator while mpv is buffering",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T13:00:11Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1ec6e89c7c345856134be910a1b0c5668d766c94",
"body": "Static \"…\" gave no signal that the player was actually doing\nsomething while ICY/media-title metadata was in flight. Replace it\nwith bubbles/spinner.Dot — the canonical braille spinner\n\"⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏\", 10 FPS — followed by a muted \"buffering\" label.\n\nSpinner ticks live globally: Init returns m.spinner.\n[…]\nrendered and the tick is a no-op.\n\nSpinner color uses the active theme's Muted tone so the animation\ndoesn't dominate the now-playing card; it reads as a quiet activity\nindicator rather than an alarm.",
"is_bot": false,
"headline": "TUI: braille spinner for buffering placeholder",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T12:56:02Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "fc64578c3f638b52884fc440c5d6f22f953b2939",
"body": "mpv's ytdl_hook briefly reports a YouTube URL tail\n(\"watch?v=jfKfPfyJRdk\") as media-title before the resolved video\ntitle arrives a few seconds later. The TUI was rendering the URL\nfragment as the track line (\"watch?v=jfKfPfyJRdk\" in muted under\nthe station name), which looks broken.\n\ntranslatePrope\n[…]\n…\" placeholder\ninstead, which then gets replaced by the real title once\nytdl_hook resolves it.\n\nTable-driven test covers the YouTube case plus a few generic URL\nshapes (http, rtmp, query-string-only).",
"is_bot": false,
"headline": "audio: suppress URL-fragment media-title from ytdl_hook",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T12:53:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5a9789d3133d7faf400c9fbc3363ac82fbf23dc6",
"body": "The \"+ add station\" affordance under the list duplicated the\n\"a add station\" entry already in the help bar — two parallel paths to\nthe same action, both visually present at the same time. Drop the\ninline line; the help-bar entry stays. The empty-list case keeps the\n\"press a to add one\" hint so a fre\n[…]\n\\n stacked with\nthe gap and inflated the spacing.\n\nNet: same elements, tighter layout, no information loss. Orphan\nAddStation style removed from styles.go since its only consumer\nwas the deleted line.",
"is_bot": false,
"headline": "TUI polish: drop \"+ add station\", tighten inter-block spacing",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T12:50:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4dc342553614ed0087fb08f131a67d6980cf0fb1",
"body": "Three small follow-ups after the previous reskin landed:\n\nformatTrack now distinguishes \"real ICY metadata\" from \"media-title\nfallback\". When mpv has both Artist and Title (the normal Icecast\ncase), the line stays in foreground + warning accent — the user is\nlistening to a song. When only Title exis\n[…]\nank line between the last station and \"+ add station\" is\nremoved too — the affordance now reads as part of the list rather\nthan a stranded line below it.\n\nTests still pass; only renderer code shifted.",
"is_bot": false,
"headline": "TUI polish: track styling, spacing, card padding",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T12:46:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "55c65a344ecaaae5edecb4da8661e369cb6af4a3",
"body": "Visual reskin pulling the UI toward modern-dashboard territory while\nkeeping it minimal: icons earn the identity, a single rounded card\nanchors the focus element, and the rest stays free-form.\n\nNow-playing wraps in a rounded-border lipgloss card (Muted border,\nhorizontal padding, MarginLeft to align\n[…]\n new\nindicator glyphs (TestView_ShowsPlayingMarker now asserts ● / ◯\ninstead of \"live\"/\"paused\"). Orphan Separator style removed from\nstyles.go — its only consumer was the now-deleted renderSeparator.",
"is_bot": false,
"headline": "TUI: nerd-font icons, now-playing card, status glyphs",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T12:42:03Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5b7eb3cfeb99f9d5f0663b042d35082bfebf4d2a",
"body": "Two real rendering bugs surfaced on the user's first screenshot:\n\nA1. Column alignment in pomodoro / today panels was busted because\nfmt.Sprintf(\"%-Ns\", styled, ...) counts ANSI escape bytes toward the\nfield width, so styled labels got zero padding (\"listened<1m\" instead\nof \"listened <1m\"). Replac\n[…]\ninstead of \"(no stations configured)\" — fresh-install users see\nthe next step without consulting docs.\n\ngo vet + go test stay clean. No exported API changes — only renders\ninside internal/tui shifted.",
"is_bot": false,
"headline": "TUI polish: column alignment, layout balance, cursor + empty states",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T12:28:07Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ad4859cab5c900224f50c4d6ad4573c326ca18dc",
"body": "The plan §5.1 mockup hinted at this with a \"+ add station\" placeholder\nin the station list, but until now the only way to add a station was\nto edit config.yaml by hand. New `a` binding pops a centered modal\nform built on bubbles/textinput: name + url, Tab cycles fields, Enter\nsaves, Esc cancels. Val\n[…]\n.\n\ngo.mod / go.sum: add the bubbles/textinput transitive dep\n(github.com/atotto/clipboard).\n\nREADME updated with the new binding and a one-liner explaining the\nmodal flow plus the auto kind detection.",
"is_bot": false,
"headline": "TUI: add-station modal form (a key)",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T12:13:59Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c1f891cb36a05dd44c00e3e58fbf00ff9b4f0385",
"body": "adrg/xdg.ConfigFile/StateFile resolves to ~/Library/Application Support\non macOS, despite the project plan §9 explicitly wanting the XDG-style\n~/.config and ~/.local/state paths on both platforms (the convention\nterminal-tool users expect). Result: the README pointed users at one\nlocation while conf\n[…]\n\nfile is absent or on Linux).\n\nREADME: rewords the config / state sections to match reality and to\nreassure users that no manual file or directory setup is needed —\nboth are auto-created on first run.",
"is_bot": false,
"headline": "Fix XDG path resolution on macOS, drop adrg/xdg dep",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T12:06:07Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5a059bfd63f08cca4323cced9bb9b3bd0c57b639",
"body": "config.Station gains a Kind field with two recognized values: \"stream\"\n(default — direct HTTP/Icecast URL passed to mpv as-is) and \"youtube\"\n(routed through mpv's built-in ytdl_hook script). Empty Kind defaults\nto \"stream\" so existing Phase 0-3 configs keep working with no changes.\nHelpers Effective\n[…]\nkey parsing back to KindStream, and YAML round-trip of\nkind: youtube. Example config gets a commented-out Lofi Girl entry as\na starting template; README documents the new requirement and config\nshape.",
"is_bot": false,
"headline": "Phase 4a: YouTube station kind via mpv's ytdl_hook",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T11:56:52Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "91c5d1f7bcb444d7584e0639e86b29a60cbca3ae",
"body": "internal/pomodoro: pure state machine — no goroutines, no time.Now()\ninside the package. Sessions store an absolute EndsAt rather than a\ncountdown so laptop sleep / suspend doesn't drift the timer; Tick\nadvances exactly one phase per call even when \"now\" is far past\nEndsAt. Stats track ListenedToday\n[…]\nts, partial overrides keep defaults for unmentioned\nfields, and `pomodoro:` (null) re-fills defaults.\n\nREADME updated to Phase 3 surface; the example config now includes the\ndocumented pomodoro block.",
"is_bot": false,
"headline": "Phase 3: pomodoro focus timer with stats and notifications",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T10:03:03Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9a0190cbc1855bfe9407a6571c4a7d9ac8450396",
"body": "Themes. Three new palettes (catppuccin-mocha, gruvbox-dark, rose-pine)\njoin Tokyo Night, all sharing the same 10-role surface. theme.Names()\nexposes a stable ordering and theme.Next drives the new `t` cycle\nbinding; switching is instantaneous because Styles is just rebuilt\nfrom the new palette.\n\nSta\n[…]\norizontally centered on the available width.\n\nREADME updated to Phase 2 surface; tests cover theme registration,\nstate round-trip + atomicity + Pomodoro preservation, and the existing\nTUI smoke suite.",
"is_bot": false,
"headline": "Phase 2: themes, state persistence, mini mode, statusline, polish",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T09:26:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5e246379d4055c7b29a2a19fc9cc37caae1e5f9b",
"body": "internal/audio: new package wrapping the mpv subprocess. ipcClient\ndemultiplexes JSON-line messages by request_id (sync.Map), serializes\nwrites through a mutex, and runs a 1 MiB-cap scanner read loop that\nroutes property-change events to a buffered channel. Player layers the\nhigh-level surface on to\n[…]\nerror responses, large lines, malformed input,\ncontext cancellation, post-close behavior); Player.translateOne for\nall event variants and dedup behavior. Manual mpv integration is the\nacceptance gate.",
"is_bot": false,
"headline": "Phase 1: real audio via mpv JSON-IPC",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T09:15:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7b20105079553e92ce6dd63aa2daab1336f1d445",
"body": "Bootstraps the Bubble Tea TUI per plan §6 Phase 0. Audio is faked —\nspace toggles a \"live\"/\"paused\" status flag on the cursor's station and\nmarks it with a ♪ glyph. Real playback arrives in Phase 1 via mpv IPC.\n\ninternal/config: YAML loader on the XDG path. Defaults() ships four\nSomaFM/Radio Paradis\n[…]\nt from §5.1.\n\nTests cover the core (config round-trip, defaults, malformed YAML;\ntheme Lookup; volume clamping, cursor bounds, play/pause toggle, help\ntoggle, quit). View rendering is verified by eye.",
"is_bot": false,
"headline": "Phase 0: UI scaffold with Tokyo Night theme",
"author_name": "Alexander Neupokoev",
"author_login": "Alexsatter",
"committed_at": "2026-04-25T08:41:19Z",
"body_truncated": true,
"is_coding_agent": false
}
],
"releases_count": 13,
"commits_last_year": 101,
"latest_release_at": "2026-06-18T09:43:49Z",
"latest_release_tag": "v0.1.12",
"releases_from_tags": false,
"days_since_last_push": 36,
"active_weeks_last_year": 5,
"days_since_latest_release": 36,
"mean_days_between_releases": 5.1
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": false,
"health_percentage": 42,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "github.com/iRootPro/lofi-player",
"exists": true,
"license": null,
"keywords": [],
"ecosystem": "go",
"matches_repo": true,
"registry_url": "https://pkg.go.dev/github.com/iRootPro/lofi-player",
"is_deprecated": false,
"latest_version": "v0.1.12",
"repository_url": "https://github.com/iRootPro/lofi-player",
"versions_count": 13,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": null,
"first_published_at": null,
"latest_published_at": "2026-06-18T09:42:44Z",
"latest_version_yanked": null,
"days_since_latest_publish": 36
}
]
},
"popularity": {
"forks": 0,
"stars": 1,
"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": false,
"has_mcp_signal": false,
"bootstrap_files": [],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [],
"toolchain_manifests": [
"go.mod"
],
"largest_source_bytes": 34574,
"source_files_sampled": 37,
"oversized_source_files": 0,
"agent_instruction_files": [],
"agent_instruction_max_bytes": null
},
"dependencies": {
"manifests": [
"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"
],
"dependencies": [
{
"name": "github.com/atotto/clipboard",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v0.1.4"
},
{
"name": "github.com/charmbracelet/bubbles",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.0.0"
},
{
"name": "github.com/charmbracelet/bubbletea",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.3.10"
},
{
"name": "github.com/charmbracelet/lipgloss",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.1.0"
},
{
"name": "gopkg.in/yaml.v3",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v3.0.1"
}
],
"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": 0,
"open_issues": 0,
"closed_ratio": null,
"closed_issues": 0,
"closed_unmerged_prs": 0
},
"bus_factor": 1,
"bot_contributors": 0,
"top_contributors": [
{
"type": "User",
"login": "Alexsatter",
"commits": 101,
"avatar_url": "https://avatars.githubusercontent.com/u/118963855?v=4"
}
],
"contributors_sampled": 1,
"top_contributor_share": 1
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"ci.yml"
],
"has_docs_dir": false,
"linter_configs": [],
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"go.sum"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 10,
"reason": "no binaries found in the repo",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 0,
"reason": "branch protection not enabled on development/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": 0,
"reason": "project has 0 contributing companies or organizations -- score normalized to 0",
"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": 0,
"reason": "no update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not 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": null,
"reason": "packaging workflow not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 0,
"reason": "dependency not pinned by hash detected -- score normalized to 0",
"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": 0,
"reason": "security policy file not 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": 8,
"reason": "2 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "9a340a77e7668a24327a26d62771287328ed94ac",
"ran_at": "2026-07-24T21:36:50Z",
"aggregate_score": 3.4,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": false,
"has_dependabot_config": false
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-06-18T09:43:40Z",
"oldest_open_prs": [],
"last_merged_pr_at": null,
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": []
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/iRootPro/lofi-player",
"host": "github.com",
"name": "lofi-player",
"owner": "iRootPro"
},
"metrics": {
"overall": {
"key": "overall",
"band": "at_risk",
"name": "Overall health",
"note": null,
"notes": [],
"value": 42,
"inputs": {
"security": 34,
"vitality": 60,
"community": 24,
"governance": 34,
"engineering": 56
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "moderate",
"name": "Vitality",
"value": 60,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "at_risk",
"name": "Development activity",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"commits_last_year": 101,
"human_commit_share": 1,
"days_since_last_push": 36,
"active_weeks_last_year": 5
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 36 days ago",
"points": 18,
"status": "partial",
"details": [
{
"code": "push_recency",
"params": {
"days": 36
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "5/52 weeks with commits",
"points": 3.5,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 5
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "101 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 101
}
}
],
"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": 13,
"latest_release_tag": "v0.1.12",
"releases_from_tags": false,
"days_since_latest_release": 36,
"mean_days_between_releases": 5.1
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "13 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 13
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 36 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 36
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~5.1 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 5.1
}
}
],
"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": 1,
"watchers": 0,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "1 stars",
"points": 0,
"status": "missed",
"details": [
{
"code": "stars",
"params": {
"count": 1
}
}
],
"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": 34,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 10,
"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 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "critical",
"name": "Issue & PR responsiveness",
"note": "Excluded from scoring (no data or not applicable): Issue resolution, PR acceptance. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"issue_resolution",
"pr_acceptance"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 1,
"inputs": {
"merged_prs": 0,
"open_issues": 0,
"closed_issues": 0,
"issue_closed_ratio": null,
"closed_unmerged_prs": 0
},
"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": "no decided pull requests or no data",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_decided_prs_or_data",
"params": {}
}
],
"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": "at_risk",
"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": 44,
"inputs": {
"followers": 0,
"owner_type": "User",
"is_verified": null,
"owner_login": "iRootPro",
"public_repos": 81,
"account_age_days": 3734
},
"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": "0 followers of iRootPro",
"points": 0,
"status": "missed",
"details": [
{
"code": "owner_followers",
"params": {
"count": 0,
"login": "iRootPro"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "81 public repos, account ~10 yr old",
"points": 25,
"status": "met",
"details": [
{
"code": "public_repos",
"params": {
"count": 81
}
},
{
"code": "account_age_years",
"params": {
"years": 10
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"github.com/iRootPro/lofi-player"
],
"ecosystems": "go",
"any_deprecated": false,
"min_days_since_publish": 36
},
"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 36 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 36
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "13 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 13
}
}
],
"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": 56,
"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": "1 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 1
}
}
],
"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": 50,
"inputs": {
"topics": [],
"has_wiki": true,
"homepage": null,
"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": null,
"points": 0,
"status": "missed",
"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": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "at_risk",
"name": "Security",
"value": 34,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "at_risk",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): CI-Tests, Packaging. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"ci_tests",
"packaging"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 34,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 16,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 2,
"scorecard_aggregate": 3.4
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection not enabled on development/release branches",
"points": 0,
"status": "missed",
"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 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"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": "no update tool detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"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 not detected",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"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 not detected",
"points": 0,
"status": "missed",
"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": "2 existing vulnerabilities detected",
"points": 6,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "moderate",
"name": "AI Readiness",
"value": 58,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "at_risk",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 0.97,
"agent_instruction_files": [],
"agent_instruction_max_bytes": null
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "no CLAUDE.md / AGENTS.md / editor rules",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_instructions",
"params": {}
}
],
"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": "97 of 100 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 97,
"sampled": 100
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "moderate",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 56,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"go.sum"
],
"has_dockerfile": false,
"typed_language": true,
"bootstrap_files": [],
"has_devcontainer": false,
"has_linter_config": false,
"typecheck_configs": [],
"agent_commit_share": 0,
"toolchain_manifests": [
"go.mod"
],
"dependency_bot_commit_share": 0
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": "go.mod (toolchain convention, no task runner)",
"points": 12.6,
"status": "partial",
"details": [
{
"code": "toolchain_convention",
"params": {
"files": "go.mod"
}
}
],
"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": "Go (statically typed)",
"points": 11,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "no agent-authored commits among the last 100",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_authored_commits",
"params": {
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "no automated dependency updates observed",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_dependency_automation",
"params": {}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "excellent",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"primary_language": "Go",
"largest_source_bytes": 34574,
"source_files_sampled": 37,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "Go (statically typed)",
"points": 45,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/37 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 37,
"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",
"GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
],
"report_type": "repository",
"generated_at": "2026-07-24T21:36:54.976794Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/i/iRootPro/lofi-player.svg",
"full_name": "iRootPro/lofi-player",
"license_state": "standard",
"license_spdx": "MIT"
}