Ver Fonte

### v1.0.0 (July 8, 2025)

- Initial release
- API Key and Session Cookie authentication
- System tray integration
- Transparent overlay widget
- Drag-and-drop positioning
- Auto-refresh functionality
- Enhanced error handling
- Quick Console access
Dalibor Votruba há 1 ano atrás
pai
commit
a348c406ac

+ 47 - 0
ClaudeCodeBalanceWidget/ClaudeCodeBalanceWidget.csproj

@@ -0,0 +1,47 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net9.0-windows</TargetFramework>
+    <Nullable>enable</Nullable>
+    <UseWindowsForms>true</UseWindowsForms>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <RootNamespace>qdr.app.tools.claudecodebalancewidget</RootNamespace>
+    <AssemblyName>claudecodebalancewidget</AssemblyName>
+    <ApplicationIcon>ClaudeCodeBalanceWidget.ico</ApplicationIcon>
+    <PackageId>qdr.app.tools.claudecodebalancewidget</PackageId>
+    <Title>ClaudeCodeBalanceWidget</Title>
+    <Authors>Dalibor Votruba</Authors>
+    <Company>Quadarax</Company>
+    <Description>Windows widget that shows current Claude Code balance</Description>
+    <PackageProjectUrl>quadarax.com/claudecodebalancewidget</PackageProjectUrl>
+    <PackageTags>AI, MONITOR, BALANCE, CLAUDE</PackageTags>
+    <SignAssembly>True</SignAssembly>
+    <AssemblyOriginatorKeyFile>D:\Projects\quadarax\qdr.app\qdr.app.tools\bo_strong_key_pair.snk</AssemblyOriginatorKeyFile>
+	<!-- Version Information -->
+    <Version>1.0.0</Version>
+    <AssemblyVersion>1.0.0.0</AssemblyVersion>
+    <FileVersion>1.0.0.0</FileVersion>
+    <InformationalVersion>1.0.0</InformationalVersion>
+    <Copyright>Quadarax (c) 2025</Copyright>
+    <PackageIcon>ClaudeCodeBalanceWidget.ico</PackageIcon>
+    <PackageReadmeFile>readme.md</PackageReadmeFile>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Include="ClaudeCodeBalanceWidget.ico" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Update="ClaudeCodeBalanceWidget.ico">
+      <Pack>True</Pack>
+      <PackagePath>\</PackagePath>
+    </None>
+    <None Update="readme.md">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      <Pack>True</Pack>
+      <PackagePath>\</PackagePath>
+    </None>
+  </ItemGroup>
+
+</Project>

BIN
ClaudeCodeBalanceWidget/ClaudeCodeBalanceWidget.ico


+ 25 - 0
ClaudeCodeBalanceWidget/ClaudeCodeBalanceWidget.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36221.1 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClaudeCodeBalanceWidget", "ClaudeCodeBalanceWidget.csproj", "{315DEEB0-5705-449E-99B3-8BA37B44A7F6}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{315DEEB0-5705-449E-99B3-8BA37B44A7F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{315DEEB0-5705-449E-99B3-8BA37B44A7F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{315DEEB0-5705-449E-99B3-8BA37B44A7F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{315DEEB0-5705-449E-99B3-8BA37B44A7F6}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {72F7A4EC-5CE1-4CEF-99F2-66729C36AE65}
+	EndGlobalSection
+EndGlobal

+ 106 - 0
ClaudeCodeBalanceWidget/EnhancedSettingsForm.Designer.cs

@@ -0,0 +1,106 @@
+namespace qdr.app.tools.claudecodebalancewidget
+{
+    partial class EnhancedSettingsForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SuspendLayout();
+            
+            // Form
+            this.ClientSize = new Size(600, 500);
+            this.Text = "Settings - Claude Balance Monitor";
+            this.StartPosition = FormStartPosition.CenterParent;
+            this.FormBorderStyle = FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            
+            // Authentication Method Selection
+            var authMethodLabel = new Label();
+            authMethodLabel.Text = "Authentication Method:";
+            authMethodLabel.Location = new Point(10, 10);
+            authMethodLabel.Size = new Size(150, 20);
+            authMethodLabel.Font = new Font("Segoe UI", 9, FontStyle.Bold);
+            this.Controls.Add(authMethodLabel);
+            
+            sessionRadioButton = new RadioButton();
+            sessionRadioButton.Text = "Session Cookie (Legacy)";
+            sessionRadioButton.Location = new Point(10, 35);
+            sessionRadioButton.Size = new Size(200, 20);
+            sessionRadioButton.Checked = true;
+            sessionRadioButton.CheckedChanged += AuthMethod_CheckedChanged;
+            this.Controls.Add(sessionRadioButton);
+            
+            apiRadioButton = new RadioButton();
+            apiRadioButton.Text = "API Key (Recommended)";
+            apiRadioButton.Location = new Point(220, 35);
+            apiRadioButton.Size = new Size(200, 20);
+            apiRadioButton.CheckedChanged += AuthMethod_CheckedChanged;
+            this.Controls.Add(apiRadioButton);
+            
+            // Tab Control
+            tabControl = new TabControl();
+            tabControl.Location = new Point(10, 65);
+            tabControl.Size = new Size(570, 350);
+            this.Controls.Add(tabControl);
+            
+            // Session Cookie Tab
+            sessionTabPage = new TabPage("Session Cookie");
+            CreateSessionTab();
+            tabControl.TabPages.Add(sessionTabPage);
+            
+            // API Key Tab
+            apiTabPage = new TabPage("API Key");
+            CreateApiTab();
+            tabControl.TabPages.Add(apiTabPage);
+            
+            // Help Tab
+            helpTabPage = new TabPage("Help");
+            CreateHelpTab();
+            tabControl.TabPages.Add(helpTabPage);
+            
+            // OK Button
+            okButton = new Button();
+            okButton.Text = "OK";
+            okButton.Location = new Point(405, 430);
+            okButton.Size = new Size(90, 30);
+            okButton.Click += OkButton_Click;
+            this.Controls.Add(okButton);
+            
+            // Cancel Button
+            cancelButton = new Button();
+            cancelButton.Text = "Cancel";
+            cancelButton.Location = new Point(505, 430);
+            cancelButton.Size = new Size(90, 30);
+            cancelButton.Click += CancelButton_Click;
+            this.Controls.Add(cancelButton);
+            
+            this.ResumeLayout(false);
+        }
+
+        #endregion
+    }
+}

+ 513 - 0
ClaudeCodeBalanceWidget/EnhancedSettingsForm.cs

@@ -0,0 +1,513 @@
+using static qdr.app.tools.claudecodebalancewidget.MainForm;
+
+namespace qdr.app.tools.claudecodebalancewidget
+{
+    public partial class EnhancedSettingsForm : Form
+    {
+        private TabControl tabControl;
+        private TabPage sessionTabPage;
+        private TabPage apiTabPage;
+        private TabPage helpTabPage;
+        
+        // Session Cookie Tab Controls
+        private TextBox sessionTokenTextBox;
+        private Button sessionShowHideButton;
+        private Button sessionTestButton;
+        private Label sessionStatusLabel;
+        private Label sessionInstructionsLabel;
+        private bool isSessionTokenVisible = false;
+        
+        // API Key Tab Controls
+        private TextBox apiKeyTextBox;
+        private Button apiShowHideButton;
+        private Button apiTestButton;
+        private Label apiStatusLabel;
+        private Label apiInstructionsLabel;
+        private bool isApiKeyVisible = false;
+        
+        // Common Controls
+        private RadioButton sessionRadioButton;
+        private RadioButton apiRadioButton;
+        private Button okButton;
+        private Button cancelButton;
+        
+        public string SessionToken { get; private set; }
+        public string ApiKey { get; private set; }
+        public AuthenticationMethod AuthMethod { get; private set; }
+
+        public EnhancedSettingsForm(string currentSessionToken, string currentApiKey, AuthenticationMethod currentAuthMethod)
+        {
+            SessionToken = currentSessionToken;
+            ApiKey = currentApiKey;
+            AuthMethod = currentAuthMethod;
+            InitializeComponent();
+            
+            sessionTokenTextBox.Text = currentSessionToken;
+            apiKeyTextBox.Text = currentApiKey;
+            
+            if (currentAuthMethod == AuthenticationMethod.ApiKey)
+            {
+                apiRadioButton.Checked = true;
+                tabControl.SelectedTab = apiTabPage;
+            }
+            else
+            {
+                sessionRadioButton.Checked = true;
+                tabControl.SelectedTab = sessionTabPage;
+            }
+        }
+
+        //private void InitializeComponent()
+        //{
+        //    this.SuspendLayout();
+            
+        //    // Form
+        //    this.ClientSize = new Size(600, 500);
+        //    this.Text = "Settings - Claude Balance Monitor";
+        //    this.StartPosition = FormStartPosition.CenterParent;
+        //    this.FormBorderStyle = FormBorderStyle.FixedDialog;
+        //    this.MaximizeBox = false;
+        //    this.MinimizeBox = false;
+            
+        //    // Authentication Method Selection
+        //    var authMethodLabel = new Label();
+        //    authMethodLabel.Text = "Authentication Method:";
+        //    authMethodLabel.Location = new Point(10, 10);
+        //    authMethodLabel.Size = new Size(150, 20);
+        //    authMethodLabel.Font = new Font("Segoe UI", 9, FontStyle.Bold);
+        //    this.Controls.Add(authMethodLabel);
+            
+        //    sessionRadioButton = new RadioButton();
+        //    sessionRadioButton.Text = "Session Cookie (Legacy)";
+        //    sessionRadioButton.Location = new Point(10, 35);
+        //    sessionRadioButton.Size = new Size(200, 20);
+        //    sessionRadioButton.Checked = true;
+        //    sessionRadioButton.CheckedChanged += AuthMethod_CheckedChanged;
+        //    this.Controls.Add(sessionRadioButton);
+            
+        //    apiRadioButton = new RadioButton();
+        //    apiRadioButton.Text = "API Key (Recommended)";
+        //    apiRadioButton.Location = new Point(220, 35);
+        //    apiRadioButton.Size = new Size(200, 20);
+        //    apiRadioButton.CheckedChanged += AuthMethod_CheckedChanged;
+        //    this.Controls.Add(apiRadioButton);
+            
+        //    // Tab Control
+        //    tabControl = new TabControl();
+        //    tabControl.Location = new Point(10, 65);
+        //    tabControl.Size = new Size(570, 350);
+        //    this.Controls.Add(tabControl);
+            
+        //    // Session Cookie Tab
+        //    sessionTabPage = new TabPage("Session Cookie");
+        //    CreateSessionTab();
+        //    tabControl.TabPages.Add(sessionTabPage);
+            
+        //    // API Key Tab
+        //    apiTabPage = new TabPage("API Key");
+        //    CreateApiTab();
+        //    tabControl.TabPages.Add(apiTabPage);
+            
+        //    // Help Tab
+        //    helpTabPage = new TabPage("Help");
+        //    CreateHelpTab();
+        //    tabControl.TabPages.Add(helpTabPage);
+            
+        //    // OK Button
+        //    okButton = new Button();
+        //    okButton.Text = "OK";
+        //    okButton.Location = new Point(405, 430);
+        //    okButton.Size = new Size(90, 30);
+        //    okButton.Click += OkButton_Click;
+        //    this.Controls.Add(okButton);
+            
+        //    // Cancel Button
+        //    cancelButton = new Button();
+        //    cancelButton.Text = "Cancel";
+        //    cancelButton.Location = new Point(505, 430);
+        //    cancelButton.Size = new Size(90, 30);
+        //    cancelButton.Click += CancelButton_Click;
+        //    this.Controls.Add(cancelButton);
+            
+        //    this.ResumeLayout(false);
+        //}
+
+        private void CreateSessionTab()
+        {
+            // Instructions
+            sessionInstructionsLabel = new Label();
+            sessionInstructionsLabel.Text = 
+                "⚠️ WARNING: Session cookie method may no longer work due to enhanced security.\n\n" +
+                "To get your session token (if still working):\n" +
+                "1. Go to https://console.anthropic.com/settings/billing\n" +
+                "2. Open Developer Tools (F12)\n" +
+                "3. Go to Application → Cookies\n" +
+                "4. Find and copy the 'sessionKey' cookie value\n\n" +
+                "Note: This method is deprecated. Use API Key instead.";
+            sessionInstructionsLabel.Location = new Point(10, 10);
+            sessionInstructionsLabel.Size = new Size(540, 120);
+            sessionInstructionsLabel.Font = new Font("Segoe UI", 9);
+            sessionInstructionsLabel.ForeColor = Color.DarkOrange;
+            sessionTabPage.Controls.Add(sessionInstructionsLabel);
+            
+            // Session Token Label
+            var sessionTokenLabel = new Label();
+            sessionTokenLabel.Text = "Session Token:";
+            sessionTokenLabel.Location = new Point(10, 140);
+            sessionTokenLabel.Size = new Size(100, 20);
+            sessionTokenLabel.Font = new Font("Segoe UI", 9, FontStyle.Bold);
+            sessionTabPage.Controls.Add(sessionTokenLabel);
+            
+            // Session Token TextBox
+            sessionTokenTextBox = new TextBox();
+            sessionTokenTextBox.Location = new Point(10, 165);
+            sessionTokenTextBox.Size = new Size(420, 23);
+            sessionTokenTextBox.UseSystemPasswordChar = true;
+            sessionTokenTextBox.Font = new Font("Consolas", 9);
+            sessionTabPage.Controls.Add(sessionTokenTextBox);
+            
+            // Session Show/Hide Button
+            sessionShowHideButton = new Button();
+            sessionShowHideButton.Text = "Show";
+            sessionShowHideButton.Location = new Point(440, 165);
+            sessionShowHideButton.Size = new Size(60, 23);
+            sessionShowHideButton.Click += SessionShowHideButton_Click;
+            sessionTabPage.Controls.Add(sessionShowHideButton);
+            
+            // Session Test Button
+            sessionTestButton = new Button();
+            sessionTestButton.Text = "Test Connection";
+            sessionTestButton.Location = new Point(10, 200);
+            sessionTestButton.Size = new Size(120, 30);
+            sessionTestButton.Click += SessionTestButton_Click;
+            sessionTabPage.Controls.Add(sessionTestButton);
+            
+            // Session Status Label
+            sessionStatusLabel = new Label();
+            sessionStatusLabel.Name = "sessionStatusLabel";
+            sessionStatusLabel.Text = "Enter your session token and click 'Test Connection'";
+            sessionStatusLabel.Location = new Point(140, 205);
+            sessionStatusLabel.Size = new Size(360, 20);
+            sessionStatusLabel.ForeColor = Color.Gray;
+            sessionTabPage.Controls.Add(sessionStatusLabel);
+        }
+
+        private void CreateApiTab()
+        {
+            // Instructions
+            apiInstructionsLabel = new Label();
+            apiInstructionsLabel.Text = 
+                "✅ RECOMMENDED: Use API Key for reliable access.\n\n" +
+                "To get your API key:\n" +
+                "1. Go to https://console.anthropic.com/\n" +
+                "2. Sign up or log in to your account\n" +
+                "3. Go to Settings → API Keys\n" +
+                "4. Click 'Create Key' and copy the generated key\n" +
+                "5. Make sure you have billing set up and credits available\n\n" +
+                "Note: API keys provide more reliable access than session cookies.";
+            apiInstructionsLabel.Location = new Point(10, 10);
+            apiInstructionsLabel.Size = new Size(540, 120);
+            apiInstructionsLabel.Font = new Font("Segoe UI", 9);
+            apiInstructionsLabel.ForeColor = Color.DarkGreen;
+            apiTabPage.Controls.Add(apiInstructionsLabel);
+            
+            // API Key Label
+            var apiKeyLabel = new Label();
+            apiKeyLabel.Text = "API Key:";
+            apiKeyLabel.Location = new Point(10, 140);
+            apiKeyLabel.Size = new Size(100, 20);
+            apiKeyLabel.Font = new Font("Segoe UI", 9, FontStyle.Bold);
+            apiTabPage.Controls.Add(apiKeyLabel);
+            
+            // API Key TextBox
+            apiKeyTextBox = new TextBox();
+            apiKeyTextBox.Location = new Point(10, 165);
+            apiKeyTextBox.Size = new Size(420, 23);
+            apiKeyTextBox.UseSystemPasswordChar = true;
+            apiKeyTextBox.Font = new Font("Consolas", 9);
+            apiTabPage.Controls.Add(apiKeyTextBox);
+            
+            // API Show/Hide Button
+            apiShowHideButton = new Button();
+            apiShowHideButton.Text = "Show";
+            apiShowHideButton.Location = new Point(440, 165);
+            apiShowHideButton.Size = new Size(60, 23);
+            apiShowHideButton.Click += ApiShowHideButton_Click;
+            apiTabPage.Controls.Add(apiShowHideButton);
+            
+            // API Test Button
+            apiTestButton = new Button();
+            apiTestButton.Text = "Test API Key";
+            apiTestButton.Location = new Point(10, 200);
+            apiTestButton.Size = new Size(120, 30);
+            apiTestButton.Click += ApiTestButton_Click;
+            apiTabPage.Controls.Add(apiTestButton);
+            
+            // API Status Label
+            apiStatusLabel = new Label();
+            apiStatusLabel.Name = "apiStatusLabel";
+            apiStatusLabel.Text = "Enter your API key and click 'Test API Key'";
+            apiStatusLabel.Location = new Point(140, 205);
+            apiStatusLabel.Size = new Size(360, 20);
+            apiStatusLabel.ForeColor = Color.Gray;
+            apiTabPage.Controls.Add(apiStatusLabel);
+            
+            // Additional API Information
+            var apiInfoLabel = new Label();
+            apiInfoLabel.Text = 
+                "💡 Tips:\n" +
+                "• API keys start with 'sk-ant-api03-'\n" +
+                "• Keep your API key secure and don't share it\n" +
+                "• You can monitor usage in the Anthropic Console\n" +
+                "• API access requires a paid plan with credits";
+            apiInfoLabel.Location = new Point(10, 240);
+            apiInfoLabel.Size = new Size(540, 80);
+            apiInfoLabel.Font = new Font("Segoe UI", 8.5f);
+            apiInfoLabel.ForeColor = Color.DarkBlue;
+            apiTabPage.Controls.Add(apiInfoLabel);
+        }
+
+        private void CreateHelpTab()
+        {
+            var helpText = new RichTextBox();
+            helpText.Text = 
+                "CLAUDE BALANCE MONITOR HELP\n\n" +
+                "AUTHENTICATION METHODS:\n\n" +
+                "1. API Key (Recommended)\n" +
+                "   ✅ Most reliable method\n" +
+                "   ✅ Official Anthropic authentication\n" +
+                "   ✅ Works with current security measures\n" +
+                "   ❌ Requires paid Anthropic Console account\n\n" +
+                "2. Session Cookie (Legacy)\n" +
+                "   ❌ May not work due to enhanced security\n" +
+                "   ❌ Prone to expiration\n" +
+                "   ❌ May trigger Forbidden errors\n" +
+                "   ✅ No additional setup if it works\n\n" +
+                "TROUBLESHOOTING:\n\n" +
+                "Forbidden Error:\n" +
+                "• Anthropic has enhanced security measures\n" +
+                "• Session cookies may no longer work\n" +
+                "• Switch to API Key authentication\n" +
+                "• Ensure you have a valid Anthropic Console account\n\n" +
+                "API Key Issues:\n" +
+                "• Verify the key starts with 'sk-ant-api03-'\n" +
+                "• Check that billing is set up in Console\n" +
+                "• Ensure you have available credits\n" +
+                "• Make sure the key has proper permissions\n\n" +
+                "No Balance Found:\n" +
+                "• Check your Anthropic Console billing page manually\n" +
+                "• Verify your account has API access\n" +
+                "• Try refreshing the widget manually\n\n" +
+                "GETTING HELP:\n" +
+                "• Check Anthropic's official documentation\n" +
+                "• Visit https://support.anthropic.com/\n" +
+                "• Ensure your account is properly set up\n\n" +
+                "Note: This tool is unofficial and may break if Anthropic changes their systems.";
+            
+            helpText.Location = new Point(10, 10);
+            helpText.Size = new Size(540, 300);
+            helpText.Font = new Font("Segoe UI", 9);
+            helpText.ReadOnly = true;
+            helpText.BackColor = SystemColors.Control;
+            helpTabPage.Controls.Add(helpText);
+        }
+
+        private void AuthMethod_CheckedChanged(object sender, EventArgs e)
+        {
+            if (sessionRadioButton.Checked)
+            {
+                tabControl.SelectedTab = sessionTabPage;
+            }
+            else if (apiRadioButton.Checked)
+            {
+                tabControl.SelectedTab = apiTabPage;
+            }
+        }
+
+        private void SessionShowHideButton_Click(object sender, EventArgs e)
+        {
+            isSessionTokenVisible = !isSessionTokenVisible;
+            sessionTokenTextBox.UseSystemPasswordChar = !isSessionTokenVisible;
+            sessionShowHideButton.Text = isSessionTokenVisible ? "Hide" : "Show";
+        }
+
+        private void ApiShowHideButton_Click(object sender, EventArgs e)
+        {
+            isApiKeyVisible = !isApiKeyVisible;
+            apiKeyTextBox.UseSystemPasswordChar = !isApiKeyVisible;
+            apiShowHideButton.Text = isApiKeyVisible ? "Hide" : "Show";
+        }
+
+        private async void SessionTestButton_Click(object sender, EventArgs e)
+        {
+            var testToken = sessionTokenTextBox.Text.Trim();
+            
+            if (string.IsNullOrEmpty(testToken))
+            {
+                sessionStatusLabel.Text = "Please enter a session token";
+                sessionStatusLabel.ForeColor = Color.Red;
+                return;
+            }
+            
+            sessionStatusLabel.Text = "Testing connection...";
+            sessionStatusLabel.ForeColor = Color.Blue;
+            sessionTestButton.Enabled = false;
+            
+            try
+            {
+                using (var testClient = new HttpClient())
+                {
+                    testClient.Timeout = TimeSpan.FromSeconds(10);
+                    testClient.DefaultRequestHeaders.Add("User-Agent", 
+                        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
+                    testClient.DefaultRequestHeaders.Add("Cookie", $"sessionKey={testToken}");
+                    testClient.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");
+                    testClient.DefaultRequestHeaders.Add("Referer", "https://console.anthropic.com/");
+                    
+                    var response = await testClient.GetAsync("https://console.anthropic.com/settings/billing");
+                    
+                    if (response.IsSuccessStatusCode)
+                    {
+                        sessionStatusLabel.Text = "✓ Connection successful!";
+                        sessionStatusLabel.ForeColor = Color.Green;
+                    }
+                    else if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
+                    {
+                        sessionStatusLabel.Text = "✗ Forbidden - Session cookies may no longer work. Try API Key.";
+                        sessionStatusLabel.ForeColor = Color.Red;
+                    }
+                    else
+                    {
+                        sessionStatusLabel.Text = $"✗ Connection failed (Status: {response.StatusCode})";
+                        sessionStatusLabel.ForeColor = Color.Red;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                sessionStatusLabel.Text = $"✗ Connection error: {ex.Message}";
+                sessionStatusLabel.ForeColor = Color.Red;
+            }
+            finally
+            {
+                sessionTestButton.Enabled = true;
+            }
+        }
+
+        private async void ApiTestButton_Click(object sender, EventArgs e)
+        {
+            var testApiKey = apiKeyTextBox.Text.Trim();
+            
+            if (string.IsNullOrEmpty(testApiKey))
+            {
+                apiStatusLabel.Text = "Please enter an API key";
+                apiStatusLabel.ForeColor = Color.Red;
+                return;
+            }
+            
+            if (!testApiKey.StartsWith("sk-ant-api03-"))
+            {
+                apiStatusLabel.Text = "API key should start with 'sk-ant-api03-'";
+                apiStatusLabel.ForeColor = Color.Orange;
+                return;
+            }
+            
+            apiStatusLabel.Text = "Testing API key...";
+            apiStatusLabel.ForeColor = Color.Blue;
+            apiTestButton.Enabled = false;
+            
+            try
+            {
+                using (var testClient = new HttpClient())
+                {
+                    testClient.Timeout = TimeSpan.FromSeconds(10);
+                    
+                    // Test with a simple API call
+                    var request = new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages");
+                    request.Headers.Add("x-api-key", testApiKey);
+                    request.Headers.Add("anthropic-version", "2023-06-01");
+                    
+                    // Simple test message
+                    var testContent = @"{
+                        ""model"": ""claude-3-5-haiku-20241022"",
+                        ""max_tokens"": 10,
+                        ""messages"": [{
+                            ""role"": ""user"",
+                            ""content"": ""Hello""
+                        }]
+                    }";
+                    
+                    request.Content = new StringContent(testContent, System.Text.Encoding.UTF8, "application/json");
+                    
+                    var response = await testClient.SendAsync(request);
+                    
+                    if (response.IsSuccessStatusCode)
+                    {
+                        apiStatusLabel.Text = "✓ API key is valid and working!";
+                        apiStatusLabel.ForeColor = Color.Green;
+                    }
+                    else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
+                    {
+                        apiStatusLabel.Text = "✗ Invalid API key";
+                        apiStatusLabel.ForeColor = Color.Red;
+                    }
+                    else if (response.StatusCode == System.Net.HttpStatusCode.PaymentRequired)
+                    {
+                        apiStatusLabel.Text = "✗ No credits available. Add funds to your account.";
+                        apiStatusLabel.ForeColor = Color.Orange;
+                    }
+                    else
+                    {
+                        apiStatusLabel.Text = $"✗ API error (Status: {response.StatusCode})";
+                        apiStatusLabel.ForeColor = Color.Red;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                apiStatusLabel.Text = $"✗ Connection error: {ex.Message}";
+                apiStatusLabel.ForeColor = Color.Red;
+            }
+            finally
+            {
+                apiTestButton.Enabled = true;
+            }
+        }
+
+        private void OkButton_Click(object sender, EventArgs e)
+        {
+            SessionToken = sessionTokenTextBox.Text.Trim();
+            ApiKey = apiKeyTextBox.Text.Trim();
+            
+            if (apiRadioButton.Checked)
+            {
+                AuthMethod = AuthenticationMethod.ApiKey;
+                if (string.IsNullOrEmpty(ApiKey))
+                {
+                    MessageBox.Show("Please enter an API key or switch to session cookie method.", 
+                        "Missing API Key", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                    return;
+                }
+            }
+            else
+            {
+                AuthMethod = AuthenticationMethod.SessionCookie;
+                if (string.IsNullOrEmpty(SessionToken))
+                {
+                    MessageBox.Show("Please enter a session token or switch to API key method.", 
+                        "Missing Session Token", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                    return;
+                }
+            }
+            
+            DialogResult = DialogResult.OK;
+            Close();
+        }
+
+        private void CancelButton_Click(object sender, EventArgs e)
+        {
+            DialogResult = DialogResult.Cancel;
+            Close();
+        }
+    }
+}

+ 120 - 0
ClaudeCodeBalanceWidget/EnhancedSettingsForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 46 - 0
ClaudeCodeBalanceWidget/MainForm.Designer.cs

@@ -0,0 +1,46 @@
+namespace qdr.app.tools.claudecodebalancewidget
+{
+    partial class MainForm
+    {
+        /// <summary>
+        ///  Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+       
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        ///  Required method for Designer support - do not modify
+        ///  the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SuspendLayout();
+            
+            // MainForm
+            this.AutoScaleDimensions = new SizeF(7F, 15F);
+            this.AutoScaleMode = AutoScaleMode.Font;
+            this.ClientSize = new Size(300, 100);
+            this.Name = "MainForm";
+            this.Text = "Claude Balance Monitor";
+            this.StartPosition = FormStartPosition.CenterScreen;
+            this.TopMost = true;
+            this.ShowInTaskbar = false;
+            this.FormBorderStyle = FormBorderStyle.None;
+            
+            // Paint event for custom drawing
+            this.Paint += MainForm_Paint;
+            
+            // Mouse events for dragging
+            this.MouseDown += MainForm_MouseDown;
+            this.MouseMove += MainForm_MouseMove;
+            this.MouseUp += MainForm_MouseUp;
+            
+            this.ResumeLayout(false);
+        }
+
+        #endregion
+    }
+}

+ 684 - 0
ClaudeCodeBalanceWidget/MainForm.cs

@@ -0,0 +1,684 @@
+using System;
+using System.Drawing;
+using System.IO;
+using System.Net.Http;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Text.RegularExpressions;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Text;
+
+namespace qdr.app.tools.claudecodebalancewidget
+{
+    public partial class MainForm : Form
+    {
+        private NotifyIcon notifyIcon;
+        private System.Windows.Forms.Timer refreshTimer;
+        private HttpClient httpClient;
+        private string sessionToken = "";
+        private string apiKey = "";
+        private decimal currentBalance = 0;
+        private bool isVisible = true;
+        private bool isDragging = false;
+        private Point dragStartPoint;
+        private DateTime lastUpdateTime = DateTime.MinValue;
+        private bool isRefreshing = false;
+        private string lastError = "";
+        private AuthenticationMethod authMethod = AuthenticationMethod.SessionCookie;
+
+        public enum AuthenticationMethod
+        {
+            SessionCookie,
+            ApiKey,
+            ConsoleOnly  // New method for API key users
+        }
+
+        public MainForm()
+        {
+            InitializeComponent();
+            InitializeSystemTray();
+            InitializeHttpClient();
+            InitializeTimer();
+            SetupTransparentWindow();
+            
+            LoadConfiguration();
+            _ = RefreshBalance();
+        }
+
+        private void SetupTransparentWindow()
+        {
+            this.BackColor = Color.Lime;
+            this.TransparencyKey = Color.Lime;
+            this.Opacity = 0.95;
+            
+            this.SetStyle(ControlStyles.AllPaintingInWmPaint | 
+                         ControlStyles.UserPaint | 
+                         ControlStyles.DoubleBuffer | 
+                         ControlStyles.ResizeRedraw |
+                         ControlStyles.SupportsTransparentBackColor, true);
+
+            this.Size = new Size(300, 100);
+        }
+
+        private void InitializeSystemTray()
+        {
+            notifyIcon = new NotifyIcon();
+            // Use the application icon instead of creating a custom one
+            notifyIcon.Icon = this.Icon ?? Icon.ExtractAssociatedIcon(Application.ExecutablePath);
+            notifyIcon.Text = "Claude Balance Monitor";
+            notifyIcon.Visible = true;
+            
+            var contextMenu = new ContextMenuStrip();
+            
+            var showHideItem = new ToolStripMenuItem("Show/Hide", null, ShowHide_Click);
+            var refreshItem = new ToolStripMenuItem("Refresh Now", null, RefreshNow_Click);
+            var openConsoleItem = new ToolStripMenuItem("Open Console Billing", null, OpenConsole_Click);
+            var settingsItem = new ToolStripMenuItem("Settings...", null, Settings_Click);
+            var aboutItem = new ToolStripMenuItem("About", null, About_Click);
+            var exitItem = new ToolStripMenuItem("Exit", null, Exit_Click);
+            
+            contextMenu.Items.Add(showHideItem);
+            contextMenu.Items.Add(refreshItem);
+            contextMenu.Items.Add(openConsoleItem);
+            contextMenu.Items.Add(new ToolStripSeparator());
+            contextMenu.Items.Add(settingsItem);
+            contextMenu.Items.Add(aboutItem);
+            contextMenu.Items.Add(new ToolStripSeparator());
+            contextMenu.Items.Add(exitItem);
+            
+            notifyIcon.ContextMenuStrip = contextMenu;
+            notifyIcon.DoubleClick += ShowHide_Click;
+        }
+
+        // Removed CreateIcon method since we're using the application icon now
+
+        private void InitializeHttpClient()
+        {
+            httpClient = new HttpClient();
+            httpClient.Timeout = TimeSpan.FromSeconds(15);
+            
+            httpClient.DefaultRequestHeaders.Add("User-Agent", 
+                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
+            httpClient.DefaultRequestHeaders.Add("Accept", 
+                "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
+            httpClient.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.5");
+            httpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
+            httpClient.DefaultRequestHeaders.Add("DNT", "1");
+            httpClient.DefaultRequestHeaders.Add("Connection", "keep-alive");
+            httpClient.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
+        }
+
+        private void InitializeTimer()
+        {
+            refreshTimer = new System.Windows.Forms.Timer();
+            refreshTimer.Interval = 300000; // 5 minutes - slower since we're using Console scraping
+            refreshTimer.Tick += async (s, e) => await RefreshBalance();
+            refreshTimer.Start();
+        }
+
+        private void LoadConfiguration()
+        {
+            try
+            {
+                string configPath = GetConfigPath();
+                string configDir = Path.GetDirectoryName(configPath);
+                
+                if (!Directory.Exists(configDir))
+                {
+                    Directory.CreateDirectory(configDir);
+                }
+                
+                if (File.Exists(configPath))
+                {
+                    var lines = File.ReadAllLines(configPath);
+                    foreach (var line in lines)
+                    {
+                        if (line.StartsWith("SessionToken="))
+                        {
+                            sessionToken = line.Substring("SessionToken=".Length).Trim();
+                        }
+                        else if (line.StartsWith("ApiKey="))
+                        {
+                            apiKey = line.Substring("ApiKey=".Length).Trim();
+                        }
+                        else if (line.StartsWith("AuthMethod="))
+                        {
+                            if (Enum.TryParse<AuthenticationMethod>(line.Substring("AuthMethod=".Length).Trim(), out var method))
+                            {
+                                authMethod = method;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    var settingsForm = new SettingsForm("");
+                    if (settingsForm.ShowDialog() == DialogResult.OK)
+                    {
+                        sessionToken = settingsForm.SessionToken;
+                        SaveConfiguration();
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                lastError = $"Config error: {ex.Message}";
+                MessageBox.Show($"Error loading configuration: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        private string GetConfigPath()
+        {
+            return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
+                               "ClaudeBalanceMonitor", "config.txt");
+        }
+
+        private void SaveConfiguration()
+        {
+            try
+            {
+                string configPath = GetConfigPath();
+                Directory.CreateDirectory(Path.GetDirectoryName(configPath));
+                
+                var lines = new List<string>
+                {
+                    $"SessionToken={sessionToken}",
+                    $"ApiKey={apiKey}",
+                    $"AuthMethod={authMethod}"
+                };
+                
+                File.WriteAllLines(configPath, lines);
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Error saving configuration: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        private async Task RefreshBalance()
+        {
+            if (isRefreshing) return;
+            
+            try
+            {
+                isRefreshing = true;
+                lastError = "";
+                
+                decimal balance = -1;
+                
+                switch (authMethod)
+                {
+                    case AuthenticationMethod.ApiKey:
+                    case AuthenticationMethod.ConsoleOnly:
+                        balance = await GetBalanceViaConsoleWithApiKey();
+                        break;
+                    case AuthenticationMethod.SessionCookie:
+                        balance = await GetBalanceViaSessionCookie();
+                        break;
+                }
+                
+                UpdateBalance(balance, balance >= 0 ? "" : lastError);
+                if (balance >= 0)
+                {
+                    lastUpdateTime = DateTime.Now;
+                }
+            }
+            catch (Exception ex)
+            {
+                UpdateBalance(-1, ex.Message);
+                Console.WriteLine($"Error refreshing balance: {ex.Message}");
+            }
+            finally
+            {
+                isRefreshing = false;
+            }
+        }
+
+        private async Task<decimal> GetBalanceViaConsoleWithApiKey()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(apiKey))
+                {
+                    lastError = "No API key configured";
+                    return -1;
+                }
+
+                // Since there's no direct API endpoint for balance, we need to:
+                // 1. First verify the API key works
+                // 2. Then try to access the Console billing page
+                
+                // Step 1: Verify API key with a minimal request
+                var isValidApiKey = await VerifyApiKey();
+                if (!isValidApiKey)
+                {
+                    return -1;
+                }
+
+                // Step 2: Try to access Console billing page
+                // This is tricky because we need to be logged in to Console
+                // For now, we'll show a message that the user needs to check manually
+                lastError = "API key valid - Check Console manually for balance";
+                return -2; // Special code to indicate "check manually"
+            }
+            catch (Exception ex)
+            {
+                lastError = $"API Error: {ex.Message}";
+                return -1;
+            }
+        }
+
+        private async Task<bool> VerifyApiKey()
+        {
+            try
+            {
+                // Make a small test request to verify the API key works
+                var request = new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages");
+                request.Headers.Add("x-api-key", apiKey);
+                request.Headers.Add("anthropic-version", "2023-06-01");
+                
+                var testContent = @"{
+                    ""model"": ""claude-3-5-haiku-20241022"",
+                    ""max_tokens"": 1,
+                    ""messages"": [{
+                        ""role"": ""user"",
+                        ""content"": ""Hi""
+                    }]
+                }";
+                
+                request.Content = new StringContent(testContent, Encoding.UTF8, "application/json");
+                
+                var response = await httpClient.SendAsync(request);
+                
+                if (response.IsSuccessStatusCode)
+                {
+                    return true;
+                }
+                else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
+                {
+                    lastError = "Invalid API key";
+                    return false;
+                }
+                else if (response.StatusCode == System.Net.HttpStatusCode.PaymentRequired)
+                {
+                    lastError = "No credits available";
+                    return false;
+                }
+                else
+                {
+                    lastError = $"API verification failed: {response.StatusCode}";
+                    return false;
+                }
+            }
+            catch (Exception ex)
+            {
+                lastError = $"API verification error: {ex.Message}";
+                return false;
+            }
+        }
+
+        private async Task<decimal> GetBalanceViaSessionCookie()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(sessionToken))
+                {
+                    lastError = "No session token configured";
+                    return -1;
+                }
+
+                // Clear existing headers
+                httpClient.DefaultRequestHeaders.Remove("Cookie");
+                httpClient.DefaultRequestHeaders.Remove("X-Requested-With");
+                httpClient.DefaultRequestHeaders.Remove("Referer");
+                
+                // Add session authentication with enhanced headers
+                httpClient.DefaultRequestHeaders.Add("Cookie", $"sessionKey={sessionToken}");
+                httpClient.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");
+                httpClient.DefaultRequestHeaders.Add("Referer", "https://console.anthropic.com/");
+                httpClient.DefaultRequestHeaders.Add("Sec-Fetch-Dest", "document");
+                httpClient.DefaultRequestHeaders.Add("Sec-Fetch-Mode", "navigate");
+                httpClient.DefaultRequestHeaders.Add("Sec-Fetch-Site", "same-origin");
+
+                var response = await httpClient.GetAsync("https://console.anthropic.com/settings/billing");
+                
+                if (response.IsSuccessStatusCode)
+                {
+                    var html = await response.Content.ReadAsStringAsync();
+                    return ExtractBalanceFromHtml(html);
+                }
+                else if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
+                {
+                    lastError = "Forbidden - Session token invalid or requires additional auth";
+                    return -1;
+                }
+                else
+                {
+                    lastError = $"HTTP {response.StatusCode}";
+                    return -1;
+                }
+            }
+            catch (Exception ex)
+            {
+                lastError = $"Session Error: {ex.Message}";
+                return -1;
+            }
+        }
+
+        private decimal ExtractBalanceFromHtml(string html)
+        {
+            // Enhanced patterns for finding credit balance
+            var patterns = new[]
+            {
+                @"Credit balance[\s\S]*?\$?([\d,]+\.?\d*)",
+                @"Available credit[\s\S]*?\$?([\d,]+\.?\d*)",
+                @"remaining[^>]*>[\s]*\$?([\d,]+\.?\d*)",
+                @"balance[^>]*>[\s]*\$?([\d,]+\.?\d*)",
+                @"credit[^>]*>[\s]*\$?([\d,]+\.?\d*)",
+                @"\$?([\d,]+\.?\d*)[^<]*remaining",
+                @"""credit_balance"":\s*""?\$?([\d,]+\.?\d*)""?",
+                @"""balance"":\s*""?\$?([\d,]+\.?\d*)""?",
+                @"data-balance=""?\$?([\d,]+\.?\d*)""?",
+                @"balance.*?(\d+\.?\d*)",
+                @"Credits?[\s\S]*?\$?([\d,]+\.?\d*)",
+                @"\$?([\d,]+\.?\d*)[\s]*credit"
+            };
+
+            foreach (var pattern in patterns)
+            {
+                var matches = Regex.Matches(html, pattern, RegexOptions.IgnoreCase);
+                foreach (Match match in matches)
+                {
+                    var balanceStr = match.Groups[1].Value.Replace(",", "");
+                    if (decimal.TryParse(balanceStr, out decimal balance) && balance >= 0)
+                    {
+                        return balance;
+                    }
+                }
+            }
+
+            lastError = "Could not find balance in Console page";
+            return -1;
+        }
+
+        private void UpdateBalance(decimal balance, string error = "")
+        {
+            if (InvokeRequired)
+            {
+                Invoke(new Action<decimal, string>(UpdateBalance), balance, error);
+                return;
+            }
+
+            currentBalance = balance;
+            lastError = error;
+            
+            if (balance >= 0)
+            {
+                notifyIcon.Text = $"Claude Balance: ${balance:F2}\nLast updated: {DateTime.Now:HH:mm:ss}\nMethod: {authMethod}";
+            }
+            else if (balance == -2) // Special case for "check manually"
+            {
+                notifyIcon.Text = $"Claude API Key Valid\nCheck Console for balance\nRight-click → Open Console";
+            }
+            else
+            {
+                notifyIcon.Text = $"Claude Balance: Error\n{(string.IsNullOrEmpty(error) ? "Unknown error" : error)}\nMethod: {authMethod}";
+            }
+            
+            if (isVisible)
+            {
+                Invalidate();
+            }
+        }
+
+        private void MainForm_Paint(object sender, PaintEventArgs e)
+        {
+            var g = e.Graphics;
+            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
+            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
+            
+            // Draw background with rounded corners
+            var rect = new Rectangle(5, 5, Width - 10, Height - 10);
+            using (var path = CreateRoundedRectangle(rect, 12))
+            {
+                using (var brush = new System.Drawing.Drawing2D.LinearGradientBrush(rect, 
+                    Color.FromArgb(200, 20, 25, 35), 
+                    Color.FromArgb(180, 40, 45, 60), 
+                    System.Drawing.Drawing2D.LinearGradientMode.Vertical))
+                {
+                    g.FillPath(brush, path);
+                }
+                
+                using (var pen = new Pen(Color.FromArgb(100, 255, 255, 255), 1))
+                {
+                    g.DrawPath(pen, path);
+                }
+            }
+            
+            // Draw balance text
+            string balanceText;
+            Color textColor;
+            
+            if (currentBalance >= 0)
+            {
+                balanceText = $"${currentBalance:F2}";
+                textColor = currentBalance > 5 ? Color.FromArgb(255, 144, 238, 144) : 
+                           currentBalance > 1 ? Color.FromArgb(255, 255, 215, 0) : 
+                           Color.FromArgb(255, 255, 140, 140);
+            }
+            else if (currentBalance == -2) // Special case for "check manually"
+            {
+                balanceText = "Check Console";
+                textColor = Color.FromArgb(255, 100, 149, 237);
+            }
+            else
+            {
+                balanceText = "Error";
+                textColor = Color.FromArgb(255, 255, 100, 100);
+            }
+            
+            // Main balance text
+            using (var font = new Font("Segoe UI", currentBalance == -2 ? 10 : 13, FontStyle.Bold))
+            using (var brush = new SolidBrush(textColor))
+            {
+                var textSize = g.MeasureString(balanceText, font);
+                var x = (Width - textSize.Width) / 2;
+                var y = currentBalance == -2 ? 18 : 12;
+                g.DrawString(balanceText, font, brush, x, y);
+            }
+            
+            // Status text
+            string statusText;
+            if (currentBalance == -2)
+            {
+                statusText = "API Key Valid - Click to open Console";
+            }
+            else if (!string.IsNullOrEmpty(lastError))
+            {
+                statusText = lastError.Length > 30 ? lastError.Substring(0, 30) + "..." : lastError;
+            }
+            else if (lastUpdateTime != DateTime.MinValue)
+            {
+                statusText = $"Updated: {lastUpdateTime:HH:mm:ss} ({authMethod})";
+            }
+            else
+            {
+                statusText = "Loading...";
+            }
+            
+            using (var font = new Font("Segoe UI", 7.5f))
+            using (var brush = new SolidBrush(Color.FromArgb(180, 200, 200, 200)))
+            {
+                var textSize = g.MeasureString(statusText, font);
+                var x = (Width - textSize.Width) / 2;
+                var y = Height - 22;
+                g.DrawString(statusText, font, brush, x, y);
+            }
+        }
+
+        private System.Drawing.Drawing2D.GraphicsPath CreateRoundedRectangle(Rectangle rect, int radius)
+        {
+            var path = new System.Drawing.Drawing2D.GraphicsPath();
+            var diameter = radius * 2;
+            
+            path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
+            path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
+            path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
+            path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
+            path.CloseFigure();
+            
+            return path;
+        }
+
+        private async void RefreshNow_Click(object sender, EventArgs e)
+        {
+            await RefreshBalance();
+        }
+
+        private void OpenConsole_Click(object sender, EventArgs e)
+        {
+            try
+            {
+                Process.Start(new ProcessStartInfo
+                {
+                    FileName = "https://console.anthropic.com/settings/billing",
+                    UseShellExecute = true
+                });
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Could not open browser: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        private void About_Click(object sender, EventArgs e)
+        {
+            MessageBox.Show(
+                $"Claude Code Balance Widget v{System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}\n\n" +
+                "⚠️ IMPORTANT: Anthropic doesn't provide a balance API endpoint.\n" +
+                "This widget can verify your API key but cannot automatically\n" +
+                "retrieve your balance. You'll need to check the Console manually.\n\n" +
+                "Created by Dalibor Votruba (Quadarax)\n\n" +
+                "Supported Methods:\n" +
+                "• API Key Verification + Manual Console Check\n" +
+                "• Session Cookie (may not work due to security)",
+                "About Claude Balance Monitor",
+                MessageBoxButtons.OK,
+                MessageBoxIcon.Information
+            );
+        }
+
+        private void ShowHide_Click(object sender, EventArgs e)
+        {
+            if (isVisible)
+            {
+                Hide();
+                isVisible = false;
+            }
+            else
+            {
+                Show();
+                WindowState = FormWindowState.Normal;
+                isVisible = true;
+            }
+        }
+
+        private void Settings_Click(object sender, EventArgs e)
+        {
+            var settingsForm = new SettingsForm(sessionToken);
+            if (settingsForm.ShowDialog() == DialogResult.OK)
+            {
+                sessionToken = settingsForm.SessionToken;
+                SaveConfiguration();
+                _ = RefreshBalance();
+            }
+        }
+
+        private void Exit_Click(object sender, EventArgs e)
+        {
+            refreshTimer?.Stop();
+            notifyIcon?.Dispose();
+            httpClient?.Dispose();
+            Application.Exit();
+        }
+
+        // Mouse event handlers for dragging and clicking
+        private void MainForm_MouseDown(object sender, MouseEventArgs e)
+        {
+            if (e.Button == MouseButtons.Left)
+            {
+                isDragging = true;
+                dragStartPoint = e.Location;
+                this.Cursor = Cursors.SizeAll;
+            }
+            else if (e.Button == MouseButtons.Right)
+            {
+                notifyIcon.ContextMenuStrip.Show(Cursor.Position);
+            }
+        }
+
+        private void MainForm_MouseMove(object sender, MouseEventArgs e)
+        {
+            if (isDragging)
+            {
+                Point screenPoint = PointToScreen(e.Location);
+                Location = new Point(screenPoint.X - dragStartPoint.X, screenPoint.Y - dragStartPoint.Y);
+            }
+            else
+            {
+                this.Cursor = currentBalance == -2 ? Cursors.Hand : Cursors.SizeAll;
+            }
+        }
+
+        private void MainForm_MouseUp(object sender, MouseEventArgs e)
+        {
+            if (e.Button == MouseButtons.Left)
+            {
+                if (isDragging)
+                {
+                    // Check if this was actually a drag or just a click
+                    var dragDistance = Math.Sqrt(Math.Pow(e.X - dragStartPoint.X, 2) + Math.Pow(e.Y - dragStartPoint.Y, 2));
+                    
+                    if (dragDistance < 5) // Less than 5 pixels = click, not drag
+                    {
+                        // This was a click, not a drag
+                        if (currentBalance == -2) // If showing "Check Console", open console on click
+                        {
+                            OpenConsole_Click(sender, e);
+                        }
+                    }
+                }
+                
+                isDragging = false;
+                this.Cursor = currentBalance == -2 ? Cursors.Hand : Cursors.Default;
+            }
+        }
+
+        protected override void OnFormClosing(FormClosingEventArgs e)
+        {
+            if (e.CloseReason == CloseReason.UserClosing)
+            {
+                e.Cancel = true;
+                Hide();
+                isVisible = false;
+            }
+            else
+            {
+                base.OnFormClosing(e);
+            }
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                refreshTimer?.Dispose();
+                notifyIcon?.Dispose();
+                httpClient?.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+    }
+}

+ 120 - 0
ClaudeCodeBalanceWidget/MainForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 19 - 0
ClaudeCodeBalanceWidget/Program.cs

@@ -0,0 +1,19 @@
+namespace qdr.app.tools.claudecodebalancewidget
+{
+    internal static class Program
+    {
+        /// <summary>
+        ///  The main entry point for the application.
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            // To customize application configuration such as set high DPI settings or default font,
+            // see https://aka.ms/applicationconfiguration.
+            ApplicationConfiguration.Initialize();
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new MainForm());
+        }
+    }
+}

+ 115 - 0
ClaudeCodeBalanceWidget/SettingsForm.Designer.cs

@@ -0,0 +1,115 @@
+namespace qdr.app.tools.claudecodebalancewidget
+{
+    partial class SettingsForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SuspendLayout();
+            
+            // Form
+            this.ClientSize = new Size(500, 250);
+            this.Text = "Settings - Claude Balance Monitor";
+            this.StartPosition = FormStartPosition.CenterParent;
+            this.FormBorderStyle = FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            
+            // Instructions Label
+            instructionsLabel = new Label();
+            instructionsLabel.Text = "To get your session token:\n" +
+                                   "1. Go to https://console.anthropic.com/settings/billing\n" +
+                                   "2. Open Developer Tools (F12)\n" +
+                                   "3. Go to Application → Cookies\n" +
+                                   "4. Find and copy the 'sessionKey' cookie value";
+            instructionsLabel.Location = new Point(10, 10);
+            instructionsLabel.Size = new Size(470, 80);
+            instructionsLabel.Font = new Font("Segoe UI", 9);
+            this.Controls.Add(instructionsLabel);
+            
+            // Session Token Label
+            var tokenLabel = new Label();
+            tokenLabel.Text = "Session Token:";
+            tokenLabel.Location = new Point(10, 100);
+            tokenLabel.Size = new Size(100, 20);
+            tokenLabel.Font = new Font("Segoe UI", 9, FontStyle.Bold);
+            this.Controls.Add(tokenLabel);
+            
+            // Session Token TextBox
+            sessionTokenTextBox = new TextBox();
+            sessionTokenTextBox.Location = new Point(10, 125);
+            sessionTokenTextBox.Size = new Size(370, 23);
+            sessionTokenTextBox.UseSystemPasswordChar = true;
+            sessionTokenTextBox.Font = new Font("Consolas", 9);
+            this.Controls.Add(sessionTokenTextBox);
+            
+            // Show/Hide Button
+            showHideButton = new Button();
+            showHideButton.Text = "Show";
+            showHideButton.Location = new Point(390, 125);
+            showHideButton.Size = new Size(60, 23);
+            showHideButton.Click += ShowHideButton_Click;
+            this.Controls.Add(showHideButton);
+            
+            // Test Connection Button
+            var testButton = new Button();
+            testButton.Text = "Test Connection";
+            testButton.Location = new Point(10, 160);
+            testButton.Size = new Size(120, 30);
+            testButton.Click += TestButton_Click;
+            this.Controls.Add(testButton);
+            
+            // Status Label
+            var statusLabel = new Label();
+            statusLabel.Name = "statusLabel";
+            statusLabel.Text = "Enter your session token and click 'Test Connection'";
+            statusLabel.Location = new Point(140, 165);
+            statusLabel.Size = new Size(350, 20);
+            statusLabel.ForeColor = Color.Gray;
+            this.Controls.Add(statusLabel);
+            
+            // OK Button
+            okButton = new Button();
+            okButton.Text = "OK";
+            okButton.Location = new Point(295, 200);
+            okButton.Size = new Size(90, 30);
+            okButton.Click += OkButton_Click;
+            this.Controls.Add(okButton);
+            
+            // Cancel Button
+            cancelButton = new Button();
+            cancelButton.Text = "Cancel";
+            cancelButton.Location = new Point(395, 200);
+            cancelButton.Size = new Size(90, 30);
+            cancelButton.Click += CancelButton_Click;
+            this.Controls.Add(cancelButton);
+            
+            this.ResumeLayout(false);
+        }
+
+        #endregion
+    }
+}

+ 169 - 0
ClaudeCodeBalanceWidget/SettingsForm.cs

@@ -0,0 +1,169 @@
+namespace qdr.app.tools.claudecodebalancewidget
+{
+    public partial class SettingsForm : Form
+    {
+        private TextBox sessionTokenTextBox;
+        private Button okButton;
+        private Button cancelButton;
+        private Button showHideButton;
+        private Label instructionsLabel;
+        private bool isTokenVisible = false;
+        
+        public string SessionToken { get; private set; }
+
+        public SettingsForm(string currentToken)
+        {
+            SessionToken = currentToken;
+            InitializeComponent();
+            sessionTokenTextBox.Text = currentToken;
+        }
+
+        //private void InitializeComponent()
+        //{
+        //    this.SuspendLayout();
+            
+        //    // Form
+        //    this.ClientSize = new Size(500, 250);
+        //    this.Text = "Settings - Claude Balance Monitor";
+        //    this.StartPosition = FormStartPosition.CenterParent;
+        //    this.FormBorderStyle = FormBorderStyle.FixedDialog;
+        //    this.MaximizeBox = false;
+        //    this.MinimizeBox = false;
+            
+        //    // Instructions Label
+        //    instructionsLabel = new Label();
+        //    instructionsLabel.Text = "To get your session token:\n" +
+        //                           "1. Go to https://console.anthropic.com/settings/billing\n" +
+        //                           "2. Open Developer Tools (F12)\n" +
+        //                           "3. Go to Application → Cookies\n" +
+        //                           "4. Find and copy the 'sessionKey' cookie value";
+        //    instructionsLabel.Location = new Point(10, 10);
+        //    instructionsLabel.Size = new Size(470, 80);
+        //    instructionsLabel.Font = new Font("Segoe UI", 9);
+        //    this.Controls.Add(instructionsLabel);
+            
+        //    // Session Token Label
+        //    var tokenLabel = new Label();
+        //    tokenLabel.Text = "Session Token:";
+        //    tokenLabel.Location = new Point(10, 100);
+        //    tokenLabel.Size = new Size(100, 20);
+        //    tokenLabel.Font = new Font("Segoe UI", 9, FontStyle.Bold);
+        //    this.Controls.Add(tokenLabel);
+            
+        //    // Session Token TextBox
+        //    sessionTokenTextBox = new TextBox();
+        //    sessionTokenTextBox.Location = new Point(10, 125);
+        //    sessionTokenTextBox.Size = new Size(370, 23);
+        //    sessionTokenTextBox.UseSystemPasswordChar = true;
+        //    sessionTokenTextBox.Font = new Font("Consolas", 9);
+        //    this.Controls.Add(sessionTokenTextBox);
+            
+        //    // Show/Hide Button
+        //    showHideButton = new Button();
+        //    showHideButton.Text = "Show";
+        //    showHideButton.Location = new Point(390, 125);
+        //    showHideButton.Size = new Size(60, 23);
+        //    showHideButton.Click += ShowHideButton_Click;
+        //    this.Controls.Add(showHideButton);
+            
+        //    // Test Connection Button
+        //    var testButton = new Button();
+        //    testButton.Text = "Test Connection";
+        //    testButton.Location = new Point(10, 160);
+        //    testButton.Size = new Size(120, 30);
+        //    testButton.Click += TestButton_Click;
+        //    this.Controls.Add(testButton);
+            
+        //    // Status Label
+        //    var statusLabel = new Label();
+        //    statusLabel.Name = "statusLabel";
+        //    statusLabel.Text = "Enter your session token and click 'Test Connection'";
+        //    statusLabel.Location = new Point(140, 165);
+        //    statusLabel.Size = new Size(350, 20);
+        //    statusLabel.ForeColor = Color.Gray;
+        //    this.Controls.Add(statusLabel);
+            
+        //    // OK Button
+        //    okButton = new Button();
+        //    okButton.Text = "OK";
+        //    okButton.Location = new Point(295, 200);
+        //    okButton.Size = new Size(90, 30);
+        //    okButton.Click += OkButton_Click;
+        //    this.Controls.Add(okButton);
+            
+        //    // Cancel Button
+        //    cancelButton = new Button();
+        //    cancelButton.Text = "Cancel";
+        //    cancelButton.Location = new Point(395, 200);
+        //    cancelButton.Size = new Size(90, 30);
+        //    cancelButton.Click += CancelButton_Click;
+        //    this.Controls.Add(cancelButton);
+            
+        //    this.ResumeLayout(false);
+        //}
+
+        private void ShowHideButton_Click(object sender, EventArgs e)
+        {
+            isTokenVisible = !isTokenVisible;
+            sessionTokenTextBox.UseSystemPasswordChar = !isTokenVisible;
+            showHideButton.Text = isTokenVisible ? "Hide" : "Show";
+        }
+
+        private async void TestButton_Click(object sender, EventArgs e)
+        {
+            var statusLabel = this.Controls.Find("statusLabel", false)[0] as Label;
+            var testToken = sessionTokenTextBox.Text.Trim();
+            
+            if (string.IsNullOrEmpty(testToken))
+            {
+                statusLabel.Text = "Please enter a session token";
+                statusLabel.ForeColor = Color.Red;
+                return;
+            }
+            
+            statusLabel.Text = "Testing connection...";
+            statusLabel.ForeColor = Color.Blue;
+            
+            try
+            {
+                using (var testClient = new HttpClient())
+                {
+                    testClient.DefaultRequestHeaders.Add("User-Agent", 
+                        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
+                    testClient.DefaultRequestHeaders.Add("Cookie", $"sessionKey={testToken}");
+                    
+                    var response = await testClient.GetAsync("https://console.anthropic.com/settings/billing");
+                    
+                    if (response.IsSuccessStatusCode)
+                    {
+                        statusLabel.Text = "✓ Connection successful!";
+                        statusLabel.ForeColor = Color.Green;
+                    }
+                    else
+                    {
+                        statusLabel.Text = $"✗ Connection failed (Status: {response.StatusCode})";
+                        statusLabel.ForeColor = Color.Red;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                statusLabel.Text = $"✗ Connection error: {ex.Message}";
+                statusLabel.ForeColor = Color.Red;
+            }
+        }
+
+        private void OkButton_Click(object sender, EventArgs e)
+        {
+            SessionToken = sessionTokenTextBox.Text.Trim();
+            DialogResult = DialogResult.OK;
+            Close();
+        }
+
+        private void CancelButton_Click(object sender, EventArgs e)
+        {
+            DialogResult = DialogResult.Cancel;
+            Close();
+        }
+    }
+}

+ 19 - 0
ClaudeCodeBalanceWidget/directory_structure.txt

@@ -0,0 +1,19 @@
+Project Directory Structure 
+Generated on: 08.07.2025 19:41:46,87 
+Root: d:\Projects\quadarax\qdr.app\qdr.app.tools\ClaudeCodeBalanceWidget 
+ 
+ClaudeCodeBalanceWidget.csproj 
+ClaudeCodeBalanceWidget.ico 
+ClaudeCodeBalanceWidget.sln 
+directory_structure.txt 
+EnhancedSettingsForm.cs 
+EnhancedSettingsForm.Designer.cs 
+EnhancedSettingsForm.resx 
+gen_dirstruct.cmd 
+gen_srcout.cmd 
+MainForm.cs 
+MainForm.Designer.cs 
+MainForm.resx 
+Program.cs 
+SettingsForm.cs 
+SettingsForm.Designer.cs 

+ 75 - 0
ClaudeCodeBalanceWidget/gen_dirstruct.cmd

@@ -0,0 +1,75 @@
+@echo off
+setlocal enabledelayedexpansion
+
+rem Generate directory structure file for Visual Studio extension project
+rem Excludes: .vs, bin, obj directories and *.user files
+
+set OUTPUT_FILE=directory_structure.txt
+set CURRENT_DIR=%CD%
+
+echo Generating directory structure...
+echo Project Directory Structure > "%OUTPUT_FILE%"
+echo Generated on: %DATE% %TIME% >> "%OUTPUT_FILE%"
+echo Root: %CURRENT_DIR% >> "%OUTPUT_FILE%"
+echo. >> "%OUTPUT_FILE%"
+
+rem Function to process directory recursively
+call :ProcessDirectory "." ""
+
+echo.
+echo Directory structure generated successfully in: %OUTPUT_FILE%
+pause
+goto :eof
+
+:ProcessDirectory
+set "dir_path=%~1"
+set "indent=%~2"
+
+rem Get directory name for display
+for %%F in ("%dir_path%") do set "dir_name=%%~nxF"
+
+rem Skip excluded directories
+if /i "%dir_name%"==".vs" goto :eof
+if /i "%dir_name%"=="bin" goto :eof
+if /i "%dir_name%"=="obj" goto :eof
+if /i "%dir_name%"=="packages" goto :eof
+if /i "%dir_name%"==".git" goto :eof
+if /i "%dir_name%"==".claude" goto :eof
+if /i "%dir_name%"==".srcout" goto :eof
+if /i "%dir_name%"=="node_modules" goto :eof
+
+rem Print current directory (except root)
+if not "%dir_path%"=="." (
+    echo %indent%%dir_name%/ >> "%OUTPUT_FILE%"
+    set "new_indent=%indent%  "
+) else (
+    set "new_indent="
+)
+
+rem Process files in current directory
+for %%F in ("%dir_path%\*") do (
+    set "file_name=%%~nxF"
+    set "file_ext=%%~xF"
+    
+    rem Skip user-specific and temporary files
+    if /i not "!file_ext!"==".user" (
+        if /i not "!file_ext!"==".suo" (
+            if /i not "!file_ext!"==".cache" (
+                if /i not "!file_name!"==".gitignore" (
+                    if /i not "!file_name!"=="Thumbs.db" (
+                        if /i not "!file_name!"=="Desktop.ini" (
+                            echo %new_indent%!file_name! >> "%OUTPUT_FILE%"
+                        )
+                    )
+                )
+            )
+        )
+    )
+)
+
+rem Process subdirectories
+for /d %%D in ("%dir_path%\*") do (
+    call :ProcessDirectory "%%D" "%new_indent%"
+)
+
+goto :eof

+ 174 - 0
ClaudeCodeBalanceWidget/gen_srcout.cmd

@@ -0,0 +1,174 @@
+@echo off
+setlocal enabledelayedexpansion
+
+echo ====================================
+echo Visual Studio Solution Files Copier
+echo ====================================
+echo.
+
+REM Get the current directory
+set "SOURCE_DIR=%CD%"
+set "TARGET_DIR=%SOURCE_DIR%\.srcout"
+
+echo Source Directory: %SOURCE_DIR%
+echo Target Directory: %TARGET_DIR%
+echo.
+
+REM Clear and create target directory
+if exist "%TARGET_DIR%" (
+    echo Clearing existing .srcout directory...
+    rmdir /s /q "%TARGET_DIR%" 2>nul
+    if exist "%TARGET_DIR%" (
+        echo Warning: Could not completely clear .srcout directory
+        timeout /t 2 >nul
+    )
+)
+
+echo Creating .srcout directory...
+mkdir "%TARGET_DIR%" 2>nul
+if not exist "%TARGET_DIR%" (
+    echo Error: Could not create .srcout directory
+    pause
+    exit /b 1
+)
+
+echo.
+echo Copying files to .srcout root directory...
+
+REM Initialize counters
+set "COPIED_COUNT=0"
+set "SKIPPED_COUNT=0"
+
+REM Function to copy files recursively, flattening directory structure
+call :CopyFilesFlat "%SOURCE_DIR%"
+
+echo.
+echo Copy operation completed!
+echo Files copied: %COPIED_COUNT%
+echo Files skipped: %SKIPPED_COUNT%
+
+goto :ContinueScript
+
+:CopyFilesFlat
+setlocal
+set "CURRENT_DIR=%~1"
+
+REM Process files in current directory
+for %%f in ("%CURRENT_DIR%\*") do (
+    if not "%%~nxf"=="" (
+        call :CopyFile "%%f"
+    )
+)
+
+REM Process subdirectories (excluding blacklisted ones)
+for /d %%d in ("%CURRENT_DIR%\*") do (
+    set "DIR_NAME=%%~nxd"
+    set "SKIP_DIR=0"
+    
+    REM Check if directory should be excluded
+    if /i "!DIR_NAME!"==".vs" set "SKIP_DIR=1"
+    if /i "!DIR_NAME!"=="bin" set "SKIP_DIR=1"
+    if /i "!DIR_NAME!"=="obj" set "SKIP_DIR=1"
+    if /i "!DIR_NAME!"=="packages" set "SKIP_DIR=1"
+    if /i "!DIR_NAME!"=="TestResults" set "SKIP_DIR=1"
+    if /i "!DIR_NAME!"==".git" set "SKIP_DIR=1"
+	if /i "!DIR_NAME!"==".claude" set "SKIP_DIR=1"
+    if /i "!DIR_NAME!"==".srcout" set "SKIP_DIR=1"
+    if /i "!DIR_NAME!"=="node_modules" set "SKIP_DIR=1"
+    if /i "!DIR_NAME!"==".nuget" set "SKIP_DIR=1"
+	if /i "!DIR_NAME!"=="@Documentation" set "SKIP_DIR=1"
+    
+    if "!SKIP_DIR!"=="0" (
+        call :CopyFilesFlat "%%d"
+    )
+)
+endlocal
+goto :eof
+
+:CopyFile
+setlocal
+set "SOURCE_FILE=%~1"
+set "FILE_NAME=%~nx1"
+set "FILE_EXT=%~x1"
+set "SKIP_FILE=0"
+
+REM Check if file should be excluded
+if /i "%FILE_EXT%"==".user" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".suo" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".cache" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".tmp" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".temp" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".log" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".pdb" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".exe" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".dll" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".png" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".jpg" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".gif" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".ico" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".snk" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".vspscc" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".vssscc" set "SKIP_FILE=1"
+if /i "%FILE_EXT%"==".scc" set "SKIP_FILE=1"
+if /i "%FILE_NAME%"==".DS_Store" set "SKIP_FILE=1"
+if /i "%FILE_NAME%"=="Thumbs.db" set "SKIP_FILE=1"
+if /i "%FILE_NAME%"=="desktop.ini" set "SKIP_FILE=1"
+
+if "%SKIP_FILE%"=="0" (
+    REM Handle duplicate filenames by adding a counter
+    set "TARGET_FILE=%TARGET_DIR%\%FILE_NAME%"
+    set "COUNTER=1"
+    
+    :CheckDuplicate
+    if exist "!TARGET_FILE!" (
+        set "BASE_NAME=%~n1"
+        set "TARGET_FILE=%TARGET_DIR%\!BASE_NAME!_!COUNTER!%FILE_EXT%"
+        set /a "COUNTER+=1"
+        goto :CheckDuplicate
+    )
+    
+    copy "%SOURCE_FILE%" "!TARGET_FILE!" >nul 2>&1
+    if !ERRORLEVEL! EQU 0 (
+        set /a "COPIED_COUNT+=1"
+        echo Copied: %FILE_NAME%
+    ) else (
+        echo Failed to copy: %FILE_NAME%
+    )
+) else (
+    set /a "SKIPPED_COUNT+=1"
+)
+endlocal
+goto :eof
+
+:ContinueScript
+
+REM Count files copied
+echo.
+echo Counting files in .srcout directory...
+set "FINAL_COUNT=0"
+for /f %%i in ('dir "%TARGET_DIR%" /b /a-d 2^>nul ^| find /c /v ""') do set "FINAL_COUNT=%%i"
+
+echo.
+echo ====================================
+echo Copy Summary:
+echo ====================================
+echo Files copied: %FINAL_COUNT%
+echo Files processed: %COPIED_COUNT%
+echo Files skipped: %SKIPPED_COUNT%
+echo Source: %SOURCE_DIR%
+echo Target: %TARGET_DIR%
+echo.
+echo All files are copied to .srcout root directory (flattened structure)
+echo Excluded directories: .vs, bin, obj, packages, TestResults, .git, .srcout, node_modules, .nuget
+echo Excluded files: *.user, *.suo, *.cache, *.tmp, *.temp, *.log, *.pdb, *.exe, *.dll, and others
+echo ====================================
+
+REM Optional: Open target directory in Explorer
+set /p "OPEN_FOLDER=Open .srcout folder in Explorer? (y/n): "
+if /i "%OPEN_FOLDER%"=="y" (
+    explorer "%TARGET_DIR%"
+)
+
+echo.
+echo Script completed. Press any key to exit...
+pause >nul

+ 177 - 0
ClaudeCodeBalanceWidget/readme.md

@@ -0,0 +1,177 @@
+# Claude Code Balance Widget
+
+**Version:** 1.0.0  
+**Release Date:** July 8, 2025  
+**Author:** Dalibor Votruba (Quadarax)  
+
+A Windows desktop widget that monitors your Claude API balance and provides quick access to Anthropic Console billing information.
+
+## 🎯 Features
+
+- **System Tray Integration** - Runs quietly in the background with notification area icon
+- **Transparent Overlay** - Modern glass-like appearance with drag-and-drop positioning
+- **Multiple Authentication Methods** - Supports both API Key and Session Cookie authentication
+- **Real-time Status** - Shows connection status and last update time
+- **Quick Console Access** - One-click access to Anthropic Console billing page
+- **Auto-refresh** - Automatically checks status every 5 minutes
+
+## ⚠️ Important Limitations
+
+**Anthropic does not provide a public API endpoint for account balance.** This widget can:
+- ✅ Verify your API key is valid and working
+- ✅ Test session cookie authentication
+- ✅ Provide quick access to Console billing page
+- ❌ **Cannot automatically retrieve your actual balance**
+
+For API Key users, the widget displays "Check Console" and provides convenient links to manually check your balance.
+
+## 🔧 System Requirements
+
+- **OS:** Windows 10/11
+- **Framework:** .NET 9.0
+- **Architecture:** x64
+- **Anthropic Account:** Console account with API access or valid session
+
+## 🚀 Installation
+
+1. Download the latest release from the releases page
+2. Extract to your preferred location
+3. Run `claudecodebalancewidget.exe`
+4. Configure authentication in Settings
+
+## ⚙️ Configuration
+
+### API Key Method (Recommended)
+1. Right-click system tray icon → **Settings**
+2. Select **"API Key (Recommended)"** tab
+3. Get your API key from [Anthropic Console](https://console.anthropic.com/) → Settings → API Keys
+4. Enter the API key (starts with `sk-ant-api03-`)
+5. Click **"Test API Key"** to verify
+6. Click **OK** to save
+
+### Session Cookie Method (Legacy)
+1. Right-click system tray icon → **Settings**
+2. Select **"Session Cookie (Legacy)"** tab
+3. Go to [Anthropic Console Billing](https://console.anthropic.com/settings/billing)
+4. Open Developer Tools (F12) → Application → Cookies
+5. Copy the `sessionKey` cookie value
+6. Enter in the widget and test
+
+> **Note:** Session cookie method may not work due to enhanced security measures.
+
+## 🎮 Usage
+
+### System Tray Menu
+- **Show/Hide** - Toggle widget visibility
+- **Refresh Now** - Manual status refresh
+- **Open Console Billing** - Direct link to billing page
+- **Settings** - Configure authentication
+- **About** - Version and help information
+- **Exit** - Close application
+
+### Widget Display
+- **Green ($XX.XX)** - Balance above $5
+- **Yellow ($XX.XX)** - Balance $1-$5
+- **Red ($XX.XX)** - Balance below $1
+- **Blue "Check Console"** - API key valid, check balance manually
+- **Red "Error"** - Authentication or connection error
+
+### Mouse Controls
+- **Left Click + Drag** - Move widget position
+- **Left Click** (when showing "Check Console") - Open billing page
+- **Right Click** - Show context menu
+
+## 🔍 Troubleshooting
+
+### Forbidden Error (Session Cookie)
+- Anthropic has enhanced security measures
+- Session cookies may no longer work
+- **Solution:** Switch to API Key authentication
+
+### API Key Issues
+- Verify key starts with `sk-ant-api03-`
+- Check billing is set up in Console
+- Ensure you have available credits
+- Verify key has proper permissions
+
+### Connection Errors
+- Check your internet connection
+- Verify Anthropic services are online
+- Try refreshing manually
+- Check firewall/antivirus settings
+
+## 📁 File Structure
+
+```
+ClaudeCodeBalanceWidget/
+├── claudecodebalancewidget.exe     # Main executable
+├── ClaudeCodeBalanceWidget.ico     # Application icon
+└── README.md                       # This file
+
+Configuration stored in:
+%APPDATA%/ClaudeBalanceMonitor/
+├── config.txt                      # Authentication settings
+└── position.txt                    # Widget position
+```
+
+## 🛠️ Development
+
+**Technology Stack:**
+- .NET 9.0 Windows Forms
+- C# with modern language features
+- HTTP client for API communication
+- Regular expressions for HTML parsing
+
+**Building from Source:**
+```bash
+git clone <repository-url>
+cd ClaudeCodeBalanceWidget
+dotnet build --configuration Release
+```
+
+**Project Structure:**
+```
+├── MainForm.cs                     # Main application logic
+├── SettingsForm.cs                 # Configuration dialog
+├── EnhancedSettingsForm.cs         # Advanced settings with tabs
+├── Program.cs                      # Application entry point
+└── ClaudeCodeBalanceWidget.csproj  # Project configuration
+```
+
+## 📋 Authentication Methods Comparison
+
+| Method | Reliability | Setup Difficulty | Auto-Balance | Security |
+|--------|-------------|------------------|--------------|----------|
+| **API Key** | ✅ High | 🟡 Medium | ❌ No* | ✅ High |
+| **Session Cookie** | ❌ Low | 🟢 Easy | ❌ No | 🟡 Medium |
+
+*No automatic balance retrieval due to API limitations
+
+## 🔗 Links
+
+- **Anthropic Console:** https://console.anthropic.com/
+- **API Documentation:** https://docs.anthropic.com/
+- **Support:** https://support.anthropic.com/
+- **Claude AI:** https://claude.ai/
+
+## 📄 License
+
+This is an unofficial tool not affiliated with Anthropic. Use at your own risk.
+
+**Copyright © 2025 Quadarax - Dalibor Votruba**
+
+## 🔄 Version History
+
+### v1.0.0 (July 8, 2025)
+- Initial release
+- API Key and Session Cookie authentication
+- System tray integration
+- Transparent overlay widget
+- Drag-and-drop positioning
+- Auto-refresh functionality
+- Enhanced error handling
+- Quick Console access
+
+---
+
+> **Disclaimer:** This is an unofficial tool. Anthropic may change their authentication methods or block automated access at any time. Always keep your API keys secure and never share them.