Skip to content

feat: add lyra track CLI command#149

Merged
GeneralD merged 5 commits into
mainfrom
fix/launchagent-path
Apr 1, 2026
Merged

feat: add lyra track CLI command#149
GeneralD merged 5 commits into
mainfrom
fix/launchagent-path

Conversation

@GeneralD

@GeneralD GeneralD commented Apr 1, 2026

Copy link
Copy Markdown
Owner

概要

再生中楽曲情報をJSON出力する lyra track サブコマンドを追加。既存の PlaybackUseCase / MetadataUseCase / LyricsUseCase を活用し、独立スクリプト (now-playing) の機能をlyra本体に統合した。

変更内容

  • lyra track サブコマンド追加(--resolve / --lyrics フラグ独立)
  • NowPlayingInfo レスポンス型(Entity)、LyricLineCodable 追加
  • NowPlayingRepository / PlaybackUseCase にワンショット fetch() 追加
  • HealthcheckCommandAsyncParsableCommand に移行、RunLoop削除
  • RootCommand@main + AsyncParsableCommand に移行、Main ターゲット廃止
  • CLAUDE.md アーキテクチャドキュメント更新

背景・動機

~/.config/bin/now-playing として独立していたスクリプトの機能をlyra本体のCLIに統合し、コードの重複を解消する。lyraの既存DI配線をそのまま活用できるため、新規登録は不要。

Closes #148

テスト計画

  • NowPlayingRepository.fetch() テスト(.info / .noInfo / .eof)
  • PlaybackUseCase.fetchNowPlaying() テスト
  • 既存テスト全362件 pass
  • lyra track 実行確認(再生なし→空JSON)
  • 音楽再生中に lyra track / lyra track -r / lyra track -l / lyra track -rl を確認

Summary by CodeRabbit

  • New Features

    • Added lyra track CLI command that outputs now-playing information as JSON
    • Added --resolve/-r and --lyrics/-l flags to the track command
  • Chores

    • Updated version to 2.5.7
    • Removed collectionkit dependency

GeneralD and others added 4 commits March 28, 2026 03:12
LaunchAgentのPATHが最小限のため/opt/homebrew/binの
yt-dlp/ffmpegが見つからない問題を修正。
findExecutableInPathでwell-known pathsを直接チェックしてから
/usr/bin/whichにフォールバック。
CLAUDE.mdにGit Workflowルールを追記。

Closes #144
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
独立スクリプト(now-playing)の機能をlyra本体に統合。
既存のPlaybackUseCase/MetadataUseCase/LyricsUseCaseを活用し、
再生中楽曲情報をJSON出力するサブコマンドを追加した。

主な変更:
- TrackCommand: --resolve(-r)でメタデータ解決、--lyrics(-l)で歌詞取得
- NowPlayingInfo: CLI出力用Codableレスポンス型
- NowPlayingRepository/PlaybackUseCase: fetch()ワンショット取得を追加
- RootCommand: @main + AsyncParsableCommandに移行、Mainターゲット廃止
- HealthcheckCommand: AsyncParsableCommandに移行しRunLoop削除

Closes #148
@GeneralD GeneralD self-assigned this Apr 1, 2026
Copilot AI review requested due to automatic review settings April 1, 2026 13:15
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8241dbc-a298-4849-8915-107b7dabb4ba

📥 Commits

Reviewing files that changed from the base of the PR and between 1895835 and f1d35a6.

📒 Files selected for processing (1)
  • Sources/CLI/Resources/version.txt
✅ Files skipped from review due to trivial changes (1)
  • Sources/CLI/Resources/version.txt

📝 Walkthrough

Walkthrough

This pull request integrates a now-playing CLI command (lyra track) into the main application. Changes include converting the CLI to async execution, adding one-shot fetch APIs to repositories and use cases, introducing a NowPlayingInfo entity type, refactoring HealthcheckCommand to async patterns, and extending test coverage for new APIs.

Changes

Cohort / File(s) Summary
CLI Infrastructure & Async Migration
Sources/CLI/Commands/RootCommand.swift, Sources/CLI/Commands/HealthcheckCommand.swift, Sources/Main/main.swift
Converted RootCommand to @main AsyncParsableCommand, removed explicit main.swift entry point. Refactored HealthcheckCommand from synchronous ParsableCommand to AsyncParsableCommand, removed manual task/lock coordination, and simplified error handling.
Track Command Implementation
Sources/CLI/Commands/TrackCommand.swift
New AsyncParsableCommand subcommand that fetches now-playing data and optionally resolves metadata (--resolve) and fetches lyrics (--lyrics), outputting JSON with NowPlayingInfo structure.
Entity Model Additions
Sources/Entity/NowPlayingInfo.swift, Sources/Entity/LyricLine.swift
Introduced NowPlayingInfo struct with title, artist, album, duration, elapsedTime, lyrics, syncedLyrics, and currentLyric properties; added Codable conformance to LyricLine.
Repository & UseCase Protocol Extensions
Sources/Domain/Dependencies/Repository/NowPlayingRepository.swift, Sources/Domain/Dependencies/UseCase/PlaybackUseCase.swift
Added async one-shot fetch methods (fetch() and fetchNowPlaying()) alongside existing streaming APIs for both repository and use case interfaces.
Implementation Updates
Sources/NowPlayingRepository/NowPlayingRepositoryImpl.swift, Sources/PlaybackUseCase/PlaybackUseCaseImpl.swift
Implemented new fetch() and fetchNowPlaying() async methods delegating to underlying data sources.
Test Coverage
Tests/NowPlayingRepositoryTests/NowPlayingRepositoryTests.swift, Tests/PlaybackUseCaseTests/PlaybackUseCaseTests.swift, Tests/TrackInteractorTests/TrackInteractorRaceTests.swift
Added unit tests for new one-shot fetch APIs and updated stubs/mocks to conform to extended protocol signatures.
Package & Dependency Management
Package.swift, Package.resolved, Sources/CLI/Resources/version.txt
Redirected executable product from Main target to CLI target, removed Main target declaration, removed collectionkit dependency, updated version to 2.5.7.
Documentation
CLAUDE.md
Updated architecture diagrams and entity documentation to reflect CLI async migration, added entity type descriptions, and documented lyra track command behavior with flag descriptions.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as TrackCommand
    participant PB as PlaybackUseCase
    participant MD as MetadataUseCase
    participant LY as LyricsUseCase
    participant Enc as JSONEncoder

    CLI->>PB: fetchNowPlaying()
    PB-->>CLI: NowPlaying (title, artist, elapsed)
    
    alt resolve flag enabled
        CLI->>MD: resolveMetadata(track)
        MD-->>CLI: [Track] candidates
        CLI->>CLI: Select resolved title/artist
    end
    
    alt lyrics flag enabled
        CLI->>LY: fetchLyrics(title, artist)
        LY-->>CLI: LyricsContent (lines with timestamps)
        CLI->>CLI: Parse syncedLyrics<br/>Compute currentLyric
    end
    
    CLI->>Enc: encode(NowPlayingInfo)
    Enc-->>CLI: JSON string
    CLI->>CLI: Print output
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Poem

🐰 A track command hops into place,
With async/await at a sprightly pace,
Now-playing info, metadata, lyrics too—
JSON output, fresh and new!
From streaming to fetches, the APIs align,
This rabbit's refactor? Truly divine! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a lyra track CLI command that outputs now-playing information as JSON, directly addressing the PR's primary objective.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #148: adds TrackCommand CLI subcommand, creates NowPlayingInfo Codable response type, implements LRC lyric parsing, adds fetch() APIs to NowPlayingRepository/PlaybackUseCase, converts HealthcheckCommand to AsyncParsableCommand, and updates RootCommand with @main AsyncParsableCommand.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #148 objectives. The removal of the Main target, version bump, Package.resolved update, and documentation changes are all necessary structural changes supporting the primary feature implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/launchagent-path

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread Sources/CLI/Commands/TrackCommand.swift

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new lyra track CLI subcommand to output the current “now playing” state as JSON, integrating existing Playback/Metadata/Lyrics layers and simplifying the CLI entrypoint by making CLI the executable target.

Changes:

  • Add lyra track (--resolve / --lyrics) and introduce NowPlayingInfo JSON response model (plus LyricLine: Codable).
  • Add one-shot retrieval APIs: NowPlayingRepository.fetch() and PlaybackUseCase.fetchNowPlaying() (with accompanying tests).
  • Migrate CLI entrypoint to @main RootCommand: AsyncParsableCommand, convert healthcheck to async, and remove the Main target.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Tests/TrackInteractorTests/TrackInteractorRaceTests.swift Updates stub to satisfy new PlaybackUseCase.fetchNowPlaying() requirement.
Tests/PlaybackUseCaseTests/PlaybackUseCaseTests.swift Adds tests for PlaybackUseCase.fetchNowPlaying() and updates repository mock to support fetch().
Tests/NowPlayingRepositoryTests/NowPlayingRepositoryTests.swift Adds coverage for NowPlayingRepository.fetch() behavior across MediaRemote results.
Sources/WallpaperDataSource/FindExecutable.swift Improves executable discovery by checking common Homebrew/system paths before falling back to which.
Sources/PlaybackUseCase/PlaybackUseCaseImpl.swift Implements fetchNowPlaying() by delegating to repository fetch().
Sources/NowPlayingRepository/NowPlayingRepositoryImpl.swift Implements one-shot fetch() via a single MediaRemote poll.
Sources/Main/main.swift Removes old entrypoint (RootCommand.main()), superseded by @main in CLI.
Sources/Entity/NowPlayingInfo.swift Adds NowPlayingInfo: Codable payload used by lyra track.
Sources/Entity/LyricLine.swift Adds Codable conformance for JSON output of synced lyric lines.
Sources/Domain/Dependencies/UseCase/PlaybackUseCase.swift Extends use case protocol with one-shot fetchNowPlaying().
Sources/Domain/Dependencies/Repository/NowPlayingRepository.swift Extends repository protocol with one-shot fetch().
Sources/CLI/Resources/version.txt Bumps CLI version string.
Sources/CLI/Commands/TrackCommand.swift New async subcommand to output now-playing info (optionally resolving metadata and/or fetching lyrics).
Sources/CLI/Commands/RootCommand.swift Makes CLI the entrypoint via @main + async root command and registers TrackCommand.
Sources/CLI/Commands/HealthcheckCommand.swift Converts to AsyncParsableCommand and removes manual RunLoop/locking.
Package.swift Removes Main executable target and makes CLI the executable product/entry target.
Package.resolved Updates resolved dependencies/pins due to package graph change.
CLAUDE.md Updates architecture documentation to reflect CLI as the entrypoint and documents track behavior/APIs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/CLI/Resources/version.txt Outdated
@@ -1 +1 @@
2.5.5
2.5.7

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version bump doesn’t follow the repo’s documented semver rule for PRs: this PR is titled feat: so it should be a minor bump (CLAUDE.md:267-270), but version.txt is bumped as a patch from 2.5.5 → 2.5.7. Please bump to the next minor (e.g., 2.6.0) instead.

Suggested change
2.5.7
2.6.0

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +59
private func resolvedMetadata(track: Track) async -> (String, String, [Track]) {
@Dependency(\.metadataUseCase) var metadataUseCase

guard resolve || lyrics else { return (track.title, track.artist, []) }

let candidates = await metadataUseCase.resolveCandidates(track: track)
let title = candidates.first?.title ?? track.title
let artist = candidates.first?.artist ?? track.artist
return (title, artist, candidates)

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--resolve and --lyrics are documented as independent flags (PR description / CLAUDE.md ‘Track command’), but this guard resolves metadata when either flag is set. As a result, lyra track --lyrics will still call MetadataUseCase.resolveCandidates and may output resolved title/artist even though --resolve wasn’t requested. Consider changing this logic so metadata resolution is controlled solely by --resolve (or update the flag help/docs to reflect that --lyrics implies resolution).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional. --lyrics requires resolveCandidates to generate clean search candidates — without it, raw MediaRemote titles (e.g. YouTube titles with brackets, noise words) produce poor LRCLIB hits. The candidates are passed directly to fetchLyrics(candidates:). The flags are independent in that -r alone resolves without fetching lyrics, and -l alone fetches lyrics (which internally needs resolution for accuracy).

Comment on lines +91 to +95
private func encode(_ info: NowPlayingInfo) -> String {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
return (try? encoder.encode(info)).flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encode(_:) silently falls back to "{}" if JSON encoding fails, which can hide real runtime issues (e.g. non-conforming Double values) and still exit with status 0. Since run() is already async throws, consider making encode throwing (or throwing a ValidationError) so failures are surfaced to the caller instead of producing potentially misleading output.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above — NowPlayingInfo is all standard Codable optionals. Encoding failure is not realistic here. The {} fallback is a defensive default, not an error suppression.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/CLI/Commands/TrackCommand.swift`:
- Around line 54-59: The current guard (guard resolve || lyrics) causes
metadataUseCase.resolveCandidates(track:) to run when only --lyrics is set;
change the control flow so resolveCandidates is invoked only when resolve is
true. Replace the guard condition with a guard resolve else { return
(track.title, track.artist, []) } (or equivalently branch on resolve first) so
that if --lyrics is provided but --resolve is not, the function returns the
original track title/artist and an empty candidates array without calling
metadataUseCase.resolveCandidates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 358bf5c9-aed7-400d-9711-b1616274a6d0

📥 Commits

Reviewing files that changed from the base of the PR and between c86f9f4 and 1895835.

📒 Files selected for processing (18)
  • CLAUDE.md
  • Package.resolved
  • Package.swift
  • Sources/CLI/Commands/HealthcheckCommand.swift
  • Sources/CLI/Commands/RootCommand.swift
  • Sources/CLI/Commands/TrackCommand.swift
  • Sources/CLI/Resources/version.txt
  • Sources/Domain/Dependencies/Repository/NowPlayingRepository.swift
  • Sources/Domain/Dependencies/UseCase/PlaybackUseCase.swift
  • Sources/Entity/LyricLine.swift
  • Sources/Entity/NowPlayingInfo.swift
  • Sources/Main/main.swift
  • Sources/NowPlayingRepository/NowPlayingRepositoryImpl.swift
  • Sources/PlaybackUseCase/PlaybackUseCaseImpl.swift
  • Sources/WallpaperDataSource/FindExecutable.swift
  • Tests/NowPlayingRepositoryTests/NowPlayingRepositoryTests.swift
  • Tests/PlaybackUseCaseTests/PlaybackUseCaseTests.swift
  • Tests/TrackInteractorTests/TrackInteractorRaceTests.swift
💤 Files with no reviewable changes (1)
  • Sources/Main/main.swift

Comment thread Sources/CLI/Commands/TrackCommand.swift
@gitar-bot

gitar-bot Bot commented Apr 1, 2026

Copy link
Copy Markdown
CI failed: 1 flaky race condition test failure in TrackInteractorRaceTests.swift where 'rapid track change cancels stale resolution' test intermittently fails due to timing-sensitive assertion on Track B resolution completion.

Overview

Analyzed 1 CI log from job 69529850652. The test suite ran 362 tests successfully with 1 failure: a flaky race condition test in TrackInteractorRaceTests.swift. This appears to be a timing-sensitive intermittent issue rather than a deterministic build or code defect introduced by the PR changes.

Failures

Race Condition Test Timeout in TrackInteractorRaceTests (confidence: medium)

  • Type: flaky_test
  • Affected jobs: 69529850652
  • Related to change: unclear
  • Root cause: Test 'rapid track change cancels stale resolution — only latest track emits resolved' at TrackInteractorRaceTests.swift:110:9 failed with 'Expectation failed: hasTrackB'. This is a timing-sensitive race condition test where the assertion expecting Track B resolution to complete did not fire within the expected window. The test framework detected this as a single failure out of 362 total tests, indicating an intermittent rather than consistent issue.
  • Suggested fix: Review the test timing parameters at TrackInteractorRaceTests.swift:110. Increase XCTestExpectation timeout values to provide more tolerance for system scheduling variations, or add a small delay to allow Track B resolution to complete. Consider whether the new 'lyra track' CLI command feature introduced in this PR adds latency to track resolution that the test wasn't designed to handle. If the test consistently passes locally, this is likely environment-dependent flakiness and can be retried.

Summary

  • Change-related failures: 0 deterministic failures directly caused by PR code
  • Infrastructure/flaky failures: 1 race condition test intermittently timing out, likely unrelated to the 'lyra track' CLI command addition but worth monitoring
  • Recommended action: Re-run CI to confirm if the test passes on retry (typical flaky test behavior). If it consistently fails, investigate TrackInteractorRaceTests timing and tolerance. The PR changes (adding CLI track command and fixing LaunchAgent paths) do not appear to directly break the build or tests.
Code Review ✅ Approved 1 resolved / 1 findings

Adds lyra track CLI command to output now-playing data in JSON format. No issues found.

✅ 1 resolved
Edge Case: JSON encoding errors silently swallowed, fallback may mislead

📄 Sources/CLI/Commands/TrackCommand.swift:91-94
In encode(_:), try? encoder.encode(info) silently discards any encoding failure and returns "{}". While NowPlayingInfo uses only standard Codable types today (so failure is unlikely), if a future field introduces an unencodable type, the command would print {} with no diagnostic, making debugging difficult. Consider logging or printing a stderr message on failure.

Tip

Comment Gitar fix CI or enable auto-apply: gitar auto-apply:on

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: now-playing CLIコマンドをlyraに統合

2 participants