Skip to content

Feature: macOS menu-bar status applet #26

Open

Description

@mm65x

Goal

Provide a small native macOS menu-bar client that consumes tokemon's versioned status document without reimplementing parsing, pricing, or aggregation.

Phased scope

Phase 1: local alpha

  • Add a native menu-bar item with a compact popover.
  • Launch the installed tokemon binary and poll tokemon status --json on a configurable interval.
  • Show one headline metric (tokens, cost, or combined) and Today, This Week, This Month, and All Time scopes.
  • Provide manual refresh, last-refresh time, an action to open the terminal dashboard, and quit.
  • Keep all parsing and pricing in tokemon; the client only decodes the versioned status document.

Phase 2: polish

  • Add configurable binary path, metric, scope, and refresh interval.
  • Distinguish missing binary, incompatible schema, empty history, and source/cache failures.
  • Document local build, installation, and upgrade steps.

Safety boundaries

  • macOS only for the first alpha; no cross-platform client in this issue.
  • No daemon, IPC service, telemetry, account access, network calls, or provider credentials.
  • Refreshing must never block the menu-bar UI.

Acceptance criteria

  • The applet runs in the macOS menu bar without a normal Dock window.
  • Polling consumes only the versioned status command and never duplicates core logic.
  • Headline metric, scope, and refresh interval are configurable.
  • Empty, unavailable, incompatible, and failed states are visibly distinct.
  • The applet has a documented local build/install path and automated contract-fixture coverage.

Activity

  1. mm65x commented on Aug 11, 2026

    @mm65x
    OwnerAuthor

    Vetted implementation scope

    This issue is now scoped as a local-only, macOS-only menu-bar client of the existing versioned status command. The applet must not duplicate source parsing, pricing, aggregation, or discovery.

    Contract hardening

    • Keep schema_version: 1 as the applet contract.
    • Report pricing availability explicitly; a zero cost must not be used to mean "pricing unavailable".
    • Add fixtures for populated, empty, malformed, future-schema, unknown-field, pricing-available, and pricing-unavailable responses.
    • Preserve compatibility by allowing unknown response fields.

    Process runner

    • Invoke the configured binary directly, never through a shell.
    • Always pass status --json --offline so the applet cannot trigger network pricing.
    • Capture stdout and stderr separately.
    • Enforce a five-second timeout and a bounded output size.
    • Run refreshes off the UI thread, allow only one active refresh, and preserve the last successful result when a refresh fails.
    • Resolve an explicitly configured executable first, then fall back to the process path.

    Scopes and metrics

    • Scopes: today, current ISO week, current month, and all time.
    • Use the CLI's existing UTC date semantics for the first version; do not introduce local-calendar drift in the applet.
    • Metrics: tokens, cost, and combined.
    • If pricing is unavailable, show an explicit unavailable state rather than $0.

    Menu-bar UI

    • Run without a normal Dock window.
    • Show the selected headline metric, scope, provider count, last refresh time, loading state, stale state, and actionable errors.
    • Provide refresh, metric selection, scope selection, preferences, dashboard launch, and quit actions.
    • Dashboard launch must also avoid shell interpolation and must not block the UI.

    Preferences

    Persist locally:

    • Binary path
    • Metric
    • Scope
    • Refresh interval

    Use safe defaults, validate paths and intervals, and fall back cleanly from invalid preferences. Launch-at-login, notifications, accounts, credentials, and cloud sync are out of scope.

    Error states

    Present distinct states for missing executable, non-executable path, timeout, non-zero exit, invalid JSON, unsupported schema, missing fields, empty history, unavailable pricing, and stale data after a failed refresh.

    Tests and CI

    • Add contract decoder, process-runner, refresh-state, preference, and metric/scope tests.
    • Add macOS build and native test coverage without signing or notarization secrets on pull requests.
    • Keep the existing Rust Linux/macOS/Windows CI intact.
    • Add manual QA cases for empty data, populated data, missing binary, timeout, malformed output, unsupported schema, and offline pricing.

    Documentation and packaging

    • Document local macOS build, installation, removal, configuration, troubleshooting, UTC scope semantics, and offline pricing behavior.
    • Keep the first milestone local-build only; signed/notarized distribution and release artifacts are a follow-up.
    • Keep the native target separate from the cross-platform Rust CLI.

    Decisions fixed for this implementation

    • Local-only alpha
    • macOS-only target
    • UTC period boundaries to match the existing CLI
    • 60-second default refresh, five-second minimum, five-second process timeout
    • No daemon, telemetry, network access, credentials, raw-log parsing, or duplicated core logic

    Definition of done

    The applet is complete when it remains responsive during refresh, consumes only the offline versioned status interface, supports all four scopes and three metrics, persists and validates its preferences, distinguishes all required failure states, passes contract/process/model/macOS tests, and has documented local installation and removal steps.

  2. mm65x commented on Aug 11, 2026

    @mm65x
    OwnerAuthor

    Implementation progress

    The scoped Phase 1 implementation is now complete in draft PR #43.

    Delivered in incremental commits:

    • Hardened the status contract so capabilities.cost reflects actual pricing availability, including offline/no-price cases.
    • Added the macOS 13+ Swift package and accessory menu-bar executable.
    • Added a direct process runner for status --json --offline with separate stdout/stderr capture, five-second timeout, bounded output, and launch/non-zero/encoding/schema errors.
    • Added UTC today, ISO-week, month, and all-time queries.
    • Added persisted executable-path, metric, scope, and refresh-interval preferences with safe defaults and validation.
    • Added background refreshes that do not block the UI, preserve the last successful report after a failure, and distinguish missing/non-executable paths, empty history, unavailable pricing, malformed output, and unsupported schemas.
    • Added headline tokens/cost/combined metrics, provider count, refresh time, manual refresh, dashboard launch, preferences, and quit actions.
    • Added contract, runner, query, preference, stale-state, and metric tests, plus a macOS CI job.
    • Documented local build, user-local installation/removal, configuration, scope semantics, offline pricing, and troubleshooting.

    Verification is green locally and on PR #43:

    • cargo fmt -- --check
    • cargo test
    • cargo clippy -- -W clippy::pedantic -A clippy::module_name_repetitions
    • cargo build --release
    • swift test (21 tests)
    • swift build -c release
    • GitHub CI: format, clippy, Linux/macOS/Windows Rust tests, and macOS menu-bar tests

    The PR remains draft for review. Signing, notarization, packaging, launch-at-login, notifications, provider toggles, and distribution are intentionally follow-up work outside this local alpha.

  3. mm65x commented on Aug 12, 2026

    @mm65x
    OwnerAuthor

    Phase 2 slice: provider filters

    Phase 1 is merged in PR #43. I’m taking the next bounded, non-release polish slice from the remaining issue scope: persisted provider filters for the menu-bar client.

    Scope

    • Persist an optional provider allow-list with the existing local preferences.
    • Expose it as a comma-separated field in Preferences…; blank means all providers.
    • Normalize whitespace, remove empty names, and de-duplicate entries before saving or invoking the CLI.
    • Pass each selected provider as a repeatable --provider argument to status --json --offline.
    • Keep filtering in the existing Rust status command; the macOS client will not parse or filter raw usage data.
    • Preserve the last successful report and existing error behavior when a filtered refresh fails.

    Acceptance criteria

    • Existing users load with no provider filter and retain current behavior.
    • A saved filter round-trips through UserDefaults in stable order.
    • Status queries contain one repeatable provider argument per normalized selection.
    • The UI documents that blank means all providers and never enables network access.
    • Unit tests cover normalization, persistence, argument construction, and refresh propagation.

    Packaging, signing, distribution, launch-at-login, notifications, and budget alerts remain separate follow-up slices.

  4. mm65x commented on Aug 12, 2026

    @mm65x
    OwnerAuthor

    Provider-filter slice is implemented in draft PR #44: https://github.com/mm65x/tokemon/pull/44\n\nThe PR persists an optional comma-separated allow-list, normalizes and de-duplicates entries, passes repeatable filters to offline status refreshes, preserves the existing last-successful-result behavior, and documents blank as all providers. Local and CI verification are green.

  5. mm65x commented on Aug 13, 2026

    @mm65x
    OwnerAuthor

    PR #44 has been merged into develop. The provider-filter slice is now integrated: persisted optional allow-list, normalization/de-duplication, repeatable offline status filters, tests, and documentation. Packaging/signing/distribution and budget notifications remain follow-up work.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions