Skip to content

narlei/logitech_action_claudecode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Claude Code Usage — Logi Actions plugin

A Logitech Logi Actions SDK plugin that shows your Claude Code subscription usage on device buttons and the Logi Options+ Actions Ring. It exposes two actions:

  • 5-hour Limit — the rolling 5-hour usage window.
  • Weekly Limit — the weekly (all-models) usage window.

Each button shows the usage percentage and a large, abbreviated reset countdown (e.g. 3h, 45m, 2d) over a color-coded fill bar (green → yellow → orange → red). No title is drawn on the button — the action name already appears on hover in the Actions Ring. Credentials are read securely from the macOS Keychain — no API key required.

This is a C# port of the Ulanzi Deck reference plugin; the usage-reading logic (Keychain → Anthropic API → unified rate-limit headers) is identical.

How it works

  1. Reads the Claude Code OAuth token from the Keychain item Claude Code-credentials (security find-generic-password), parsing claudeAiOauth.accessToken.
  2. Sends a tiny throwaway request to https://api.anthropic.com/v1/messages (claude-haiku-4-5, max_tokens: 1) with the anthropic-beta: oauth-2025-04-20 header, via curl (see the runtime note below).
  3. Reads the response headers and renders them:
    • anthropic-ratelimit-unified-5h-utilization / -5h-reset
    • anthropic-ratelimit-unified-7d-utilization / -7d-reset
  4. On 401/403 it triggers a one-shot claude -p … --max-budget-usd 0.01 to refresh the OAuth token (aborts before spending tokens), then retries once.

A single shared poller refreshes every 5 minutes; pressing a button forces an immediate refresh.

Prerequisites

  • macOS with Logi Options+ installed (provides LogiPluginService.app and PluginApi.dll).
  • .NET 8 SDKbrew install dotnet@8 (set DOTNET_ROOT if prompted: export DOTNET_ROOT=/opt/homebrew/opt/dotnet@8/libexec). Verify with dotnet --version.
  • A supported device: Logitech MX Creative Console, or an MX mouse/keyboard using the Actions Ring. (Loupedeck CT/Live and Razer Stream Controllers also work.)
  • Claude Code logged in (claude CLI authenticated) so the Keychain item exists.

Build & install (dev)

make build      # dotnet build -c Release; writes the dev .link automatically
make restart    # restarts LogiPluginService so it loads the new build
# or both at once:
make deploy

make build writes a .link file (containing the absolute path to ClaudeCodeUsage/Release) into ~/Library/Application Support/Logi/LogiPluginService/Plugins/, and copies the package metadata next to the binaries. After make restart, open Logi Options+ → Actions Ring (or your device's button config) and look for the Claude Code category with the 5-hour Limit and Weekly Limit actions.

Verify it loaded:

tail ~/Library/Application\ Support/Logi/LogiPluginService/Logs/plugin_logs/ClaudeCodeUsage.log
# expect: "Plugin 'ClaudeCodeUsage' version '1.0.0' loaded ... 2 dynamic actions loaded"

Distribution (.lplug4)

make package      # builds dist/ClaudeCodeUsage.lplug4

A .lplug4 is just a zip with a metadata/ folder (the LoupedeckPackage.yaml manifest + icon) and a bin/ folder. Double-clicking it installs the plugin in Logi Options+ 5.0+. The package ships only ClaudeCodeUsage.dll (+ its .deps.json) — PluginApi.dll, SkiaSharp and the other host assemblies are provided by the Logi Plugin Service at runtime, so they are intentionally left out (verified: the plugin loads from the minimal package).

For Marketplace submission, validate the package with the Logi Plugin Tool from the Developer page.

Project layout

ClaudeCodeUsage/
  ClaudeCodeUsage.csproj          # net8.0, references PluginApi.dll, dev-install targets
  src/
    ClaudeCodeUsage.cs            # Plugin entry point (universal, no target app)
    ClaudeCodeUsageApplication.cs # REQUIRED ClientApplication subclass (see notes)
    UsageCommandBase.cs           # shared action behavior (poll subscribe + render)
    FiveHourCommand.cs            # "5-hour Limit" action
    WeeklyCommand.cs              # "Weekly Limit" action
    UsageFetcher.cs               # Keychain + curl + header parsing + CLI refresh
    UsagePoller.cs                # shared 5-min poller, raises Updated event
    UsageData.cs                  # rate-limit snapshot model
    Renderer.cs                   # BitmapBuilder drawing (bar, %, reset, states)
    PluginLog.cs                  # SDK log helper
  package/metadata/
    LoupedeckPackage.yaml         # plugin manifest
    Icon256x256.png               # package icon (regenerate: make icon)
tools/
  generate_icon.py                # pure-stdlib PNG generator for the package icon

Notes / gotchas learned the hard way

These are non-obvious requirements of the Logi Plugin Service host. Getting any of them wrong produces an unhelpful Cannot load plugin from … in plugin_logs/ClaudeCodeUsage.log.

  1. A ClientApplication subclass is mandatory — even for a universal plugin (HasNoApplication => true). Without ClaudeCodeUsageApplication, the host fails with "Cannot load plugin". This was the single hardest bug to find.

  2. No System.Net.Http, no System.Text.Json. The host is a trimmed single-file .NET runtime that does not ship those assemblies; referencing them makes the plugin fail to load. We use curl for the request and a tiny manual parser for the keychain JSON instead. System.Diagnostics.Process is available (used for security, curl, and claude).

  3. The dev .link file must contain an absolute path to the package root (the folder holding bin/ and metadata/). The csproj writes it with WriteLinesToFile — the Windows-style echo …\ from the SDK sample produces a mangled relative path on macOS.

  4. Naming must be consistent: assembly name = root namespace last segment = Plugin class name (here all ClaudeCodeUsagePlugin). A mismatch makes the host hang at "Started loading … type ''". Note these three are independent of the yaml name field (ClaudeCodeUsage) and the log file name (which follows name, not the assembly) — see gotcha #8.

  5. PluginApi.dll is copied into the build (no <Private>false</Private>), matching the official sample, so the plugin's .deps.json resolves correctly.

  6. "disabled as it had crashed before": once a plugin crashes, Logi Options+ remembers that plugin identity and refuses to load it again, even after a fix — and the flag survives service/Options+ restarts (it lives in an Options+ leveldb store). The pragmatic fix is to bump the plugin identity (assembly + name); that's why this project is ClaudeCodeUsage rather than ClaudeUsage.

  7. macOS only. The token lives in the macOS Keychain, and the Logi Actions Node.js SDK is currently Windows-only — hence the C# SDK (works on macOS via the host's bundled .NET runtime).

  8. Marketplace validator: two opposing name rules. The contribute form inspects the .lplug4 (not its outer filename) and enforces, on two different yaml fields:

    • pluginFileName (the DLL / assembly) must end with Plugin — e.g. ClaudeCodeUsagePlugin.dll. ("Plugin file name must end with 'Plugin'".)
    • name (the marketplace identity) must match ^[a-zA-Z0-9\-_.]+$ and must NOT end with Plugin — e.g. ClaudeCodeUsage. ("Invalid name … name cannot end with 'Plugin'".)

    So the assembly is ClaudeCodeUsagePlugin while the yaml name stays ClaudeCodeUsage. This is the SDK template convention (name: MyTest + MyTestPlugin.dll); the original all-ClaudeCodeUsage setup loaded locally but was rejected by the marketplace.

For Marketplace distribution, package as .lplug4 per the Distributing the plugin docs. (For distribution you can drop the copied PluginApi.dll from the package.)

References

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors