<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Advanced Cognitive Systems</title>
    <description>The latest articles on DEV Community by Advanced Cognitive Systems (@advanced_cognitivesystem).</description>
    <link>https://dev.to/advanced_cognitivesystem</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4209267%2F6425c007-ad59-44e7-aebe-081ac69c0445.png</url>
      <title>DEV Community: Advanced Cognitive Systems</title>
      <link>https://dev.to/advanced_cognitivesystem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/advanced_cognitivesystem"/>
    <language>en</language>
    <item>
      <title>I put my Claude usage limits on my desktop (a PowerShell WPF widget and a SwiftBar plugin)</title>
      <dc:creator>Advanced Cognitive Systems</dc:creator>
      <pubDate>Tue, 27 Oct 2026 08:52:19 +0000</pubDate>
      <link>https://dev.to/advanced_cognitivesystem/i-put-my-claude-usage-limits-on-my-desktop-a-powershell-wpf-widget-and-a-swiftbar-plugin-ni4</link>
      <guid>https://dev.to/advanced_cognitivesystem/i-put-my-claude-usage-limits-on-my-desktop-a-powershell-wpf-widget-and-a-swiftbar-plugin-ni4</guid>
      <description>&lt;p&gt;If you use Claude a lot, you know the message: &lt;em&gt;"You've reached your usage limit."&lt;/em&gt; It always shows up in the middle of something.&lt;/p&gt;

&lt;p&gt;claude.ai shows how close you are under &lt;strong&gt;Settings → Usage&lt;/strong&gt;, but I never think to open that page until it's too late. So I built two small widgets that keep the numbers in sight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;on &lt;strong&gt;Windows&lt;/strong&gt;, a floating mini-window,&lt;/li&gt;
&lt;li&gt;on &lt;strong&gt;macOS&lt;/strong&gt;, a menu bar item.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are a single script each, need no install beyond what ships with the OS, and are open source: 👉 &lt;strong&gt;&lt;a href="https://github.com/advancedcognitivesystems-ai/claude-usage-widgets" rel="noopener noreferrer"&gt;github.com/advancedcognitivesystems-ai/claude-usage-widgets&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzgj7q572yaqampd3q6n5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzgj7q572yaqampd3q6n5.png" alt="The Windows widget showing the 5-hour session and weekly limits" width="541" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it shows
&lt;/h2&gt;

&lt;p&gt;The same three things as the Usage page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Current session (5 h)&lt;/strong&gt;: the rolling 5-hour window&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week, all models&lt;/strong&gt;: the weekly cap&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week, Opus / Sonnet&lt;/strong&gt;: per-model weekly caps, when your plan has them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each limit has a progress bar and a countdown to its reset. The color changes as you get closer: green under 70 %, amber up to 90 %, red above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp4rtmgso86dm0zubtph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp4rtmgso86dm0zubtph.png" alt="The widget at 94 % of the session limit: the bar turns red" width="541" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rest of this post covers how it works, and a few tricks that were fun to find.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the numbers come from
&lt;/h2&gt;

&lt;p&gt;There's no public API for this, so the widgets call the same endpoints as the claude.ai web app, authenticated with your browser's &lt;code&gt;sessionKey&lt;/code&gt; cookie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://claude.ai/api/organizations
GET https://claude.ai/api/organizations/{org_uuid}/usage
Cookie: sessionKey=sk-ant-sid...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first call finds your organization (the one with the &lt;code&gt;chat&lt;/code&gt; capability), and its UUID is cached. The second returns one entry per limit. Trimmed to the fields the widgets use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"five_hour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"utilization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resets_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-26T16:50:00Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seven_day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"utilization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resets_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-30T08:00:00Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seven_day_opus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"utilization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resets_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-30T08:00:00Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seven_day_sonnet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all the widgets need: a percentage and a timestamp. They refresh every 5 minutes, and the countdowns are recomputed locally every minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; if you use Claude Code and haven't saved a session key, both widgets fall back to its OAuth token (&lt;code&gt;~/.claude/.credentials.json&lt;/code&gt;) and call &lt;code&gt;api.anthropic.com/api/oauth/usage&lt;/code&gt;, which returns the same shape.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ These are &lt;strong&gt;undocumented internal endpoints&lt;/strong&gt;. They can change any day, and this project isn't affiliated with Anthropic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Windows: a WPF app in one PowerShell script
&lt;/h2&gt;

&lt;p&gt;I wanted something people could run without installing .NET SDKs, Node or Python. Every Windows 10/11 machine has Windows PowerShell 5.1, and PowerShell can load WPF. So the whole widget is one &lt;code&gt;.ps1&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  The UI is a XAML string
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationFramework&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationCore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WindowsBase&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$xaml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sh"&gt;@'
&amp;lt;Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        WindowStyle="None" AllowsTransparency="True" Background="Transparent"
        Topmost="True" ShowInTaskbar="False" Width="270" SizeToContent="Height"&amp;gt;
  &amp;lt;Border CornerRadius="10" Background="#EE1F1E1D" Padding="14,10,14,10"&amp;gt;
    ...
  &amp;lt;/Border&amp;gt;
&amp;lt;/Window&amp;gt;
'@&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$win&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Markup.XamlReader&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Xml.XmlNodeReader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$xaml&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;WindowStyle="None"&lt;/code&gt; with &lt;code&gt;AllowsTransparency="True"&lt;/code&gt; gives you the borderless, rounded-corner card. A single &lt;code&gt;$win.DragMove()&lt;/code&gt; in a mouse handler makes it draggable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't block the UI thread
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Invoke-RestMethod&lt;/code&gt; is synchronous. Called from a timer on the UI thread, the window freezes for the duration of the request. The fix is a background runspace, plus a &lt;code&gt;DispatcherTimer&lt;/code&gt; that checks whether it's done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;powershell&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fetch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddArgument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sessionKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddArgument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orgId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ps&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BeginInvoke&lt;/span&gt;&lt;span class="err"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$poll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Windows.Threading.DispatcherTimer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$poll&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;FromMilliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$poll&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_Tick&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsCompleted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Handle-Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$res&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# back on the UI thread, safe to touch controls&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;Dispatcher.Invoke&lt;/code&gt; gymnastics: the background part only returns data, and all UI updates happen in the timer's tick, on the UI thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storing the secret with DPAPI, in one line
&lt;/h3&gt;

&lt;p&gt;The session key shouldn't sit in plain text in a config file. PowerShell's &lt;code&gt;SecureString&lt;/code&gt; cmdlets use Windows DPAPI under the hood, so this is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# save: encrypted, only decryptable by the same Windows user on the same machine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SessionKeyEnc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertTo-SecureString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AsPlainText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-SecureString&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# load&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ss&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertTo-SecureString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SessionKeyEnc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Small touches
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single instance&lt;/strong&gt;: a named mutex (&lt;code&gt;Local\ClaudeUsageWidget&lt;/code&gt;). If it already exists, the script exits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No console window&lt;/strong&gt;: &lt;code&gt;.ps1&lt;/code&gt; files open a console when you double-click them. A three-line &lt;code&gt;.vbs&lt;/code&gt; launcher starts PowerShell with &lt;code&gt;-WindowStyle Hidden&lt;/code&gt;, so there's no flash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch at startup&lt;/strong&gt;: the right-click menu creates or removes a shortcut in the Startup folder through &lt;code&gt;WScript.Shell&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  macOS: a SwiftBar plugin in JavaScript for Automation
&lt;/h2&gt;

&lt;p&gt;On macOS, &lt;a href="https://swiftbar.app" rel="noopener noreferrer"&gt;SwiftBar&lt;/a&gt; turns any executable into a menu bar item: it runs your script on a schedule and turns whatever it prints into a menu. The refresh interval goes in the file name: &lt;code&gt;claude-usage.5m.js&lt;/code&gt; runs every 5 minutes.&lt;/p&gt;

&lt;p&gt;I didn't want to require Node or Python either, so the plugin is written in &lt;strong&gt;JXA&lt;/strong&gt; (JavaScript for Automation), which ships with every Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!/usr/bin/osascript -l JavaScript
&lt;/span&gt;&lt;span class="nx"&gt;ObjC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Foundation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;currentApplication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includeStandardAdditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is plain text, one menu line per line, with options after a &lt;code&gt;|&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5h 42% · 7d 63% | sfimage=gauge.medium
---
Current session (5 h)   42 % | color=#32CA1B
▰▰▰▰▱▱▱▱▱▱   Resets in 2 h 17 | font=Menlo size=11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line goes in the menu bar; everything after &lt;code&gt;---&lt;/code&gt; goes in the dropdown. &lt;code&gt;sfimage=&lt;/code&gt; gives you any SF Symbol for free, and the menu bar icon turns amber or red when a limit goes above 70 % or 90 %.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fethxkhqkfzk0yf64o50d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fethxkhqkfzk0yf64o50d.png" alt="The macOS menu bar dropdown (rendered from the plugin's output)" width="799" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Secrets and HTTP
&lt;/h3&gt;

&lt;p&gt;The session key is stored in the macOS Keychain with the &lt;code&gt;security&lt;/code&gt; command-line tool, so there's no plain-text file to leak. JXA has no HTTP client I wanted to fight with, so requests go through &lt;code&gt;curl&lt;/code&gt;, called with &lt;code&gt;doShellScript&lt;/code&gt;. A handy curl flag: &lt;code&gt;-w "\n%{http_code}"&lt;/code&gt; appends the status code to the body, so one call gives you both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I'd tell you before you install it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A session key is full access to your claude.ai account.&lt;/strong&gt; Treat it like a password. The widgets only ever send it to &lt;code&gt;claude.ai&lt;/code&gt;, and store it encrypted (DPAPI or Keychain), but read the scripts before running them: they're short.&lt;/li&gt;
&lt;li&gt;When it shows &lt;strong&gt;401/403&lt;/strong&gt;, the key has expired (logging out of claude.ai does that). Paste a new one from the widget's menu.&lt;/li&gt;
&lt;li&gt;It's bilingual (English and French), following the system language by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Grab the zip for your OS from the &lt;a href="https://github.com/advancedcognitivesystems-ai/claude-usage-widgets/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;, or clone the repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: unzip, double-click &lt;code&gt;Lancer-Widget.vbs&lt;/code&gt;, paste your session key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;: unzip, right-click &lt;code&gt;installer.command&lt;/code&gt; → Open. It installs SwiftBar with Homebrew if needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Issues and PRs are welcome, especially if the API changes under us, or if you want to add a language. And if you're on Linux: a &lt;a href="https://github.com/Alexays/Waybar" rel="noopener noreferrer"&gt;Waybar&lt;/a&gt; or GNOME extension version would be a fun PR. 🙂&lt;/p&gt;

&lt;p&gt;-- &lt;br&gt;
ghalaax&lt;/p&gt;

</description>
      <category>claude</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
