diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml new file mode 100644 index 0000000..eede1a5 --- /dev/null +++ b/.github/workflows/build-check.yml @@ -0,0 +1,19 @@ +name: Build Check + +on: + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Build + run: dotnet build AgentsPanelExtension.sln --configuration Debug -p:Platform=x64 diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..1b4809c --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,41 @@ +name: Deploy Pages + +on: + push: + branches: [main] + paths: + - 'docs/**' + - '.github/workflows/deploy-pages.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment; let an in-progress run finish. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Upload docs/ as Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release-msix.yml b/.github/workflows/release-msix.yml new file mode 100644 index 0000000..e05f557 --- /dev/null +++ b/.github/workflows/release-msix.yml @@ -0,0 +1,162 @@ +name: Build and Release MSIX + +on: + workflow_dispatch: + inputs: + version: + description: 'Version number (leave empty to auto-detect from csproj)' + required: false + type: string + release_notes: + description: 'What is new in this version' + required: false + default: 'Updates happened.' + type: string + +env: + DISPLAY_NAME: Agents Panel + EXTENSION_NAME: AgentsPanelExtension + FOLDER_NAME: AgentsPanelExtension + +jobs: + build: + runs-on: windows-2022 + permissions: + contents: write + actions: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET 9 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Get version from project + id: version + run: | + if ("${{ github.event.inputs.version }}" -ne "") { + $version = "${{ github.event.inputs.version }}" + } else { + $projectFile = "${{ env.FOLDER_NAME }}/${{ env.EXTENSION_NAME }}.csproj" + $xml = [xml](Get-Content $projectFile) + $version = $xml.Project.PropertyGroup.AppxPackageVersion | Select-Object -First 1 + if (-not $version) { throw "Version not found in project file" } + } + echo "VERSION=$version" >> $env:GITHUB_OUTPUT + Write-Host "Using version: $version" + shell: pwsh + + - name: Build x64 MSIX + run: | + Set-Location "${{ env.FOLDER_NAME }}" + dotnet build --configuration Release ` + -p:GenerateAppxPackageOnBuild=true ` + -p:Platform=x64 ` + -p:RuntimeIdentifier=win-x64 ` + -p:AppxPackageDir="AppPackages\x64\" ` + -p:PublishTrimmed=false + shell: pwsh + + - name: Build ARM64 MSIX + run: | + Set-Location "${{ env.FOLDER_NAME }}" + dotnet build --configuration Release ` + -p:GenerateAppxPackageOnBuild=true ` + -p:Platform=ARM64 ` + -p:RuntimeIdentifier=win-arm64 ` + -p:AppxPackageDir="AppPackages\ARM64\" ` + -p:PublishTrimmed=false + shell: pwsh + + - name: Find MSIX files + id: msix + run: | + Set-Location "${{ env.FOLDER_NAME }}" + Write-Host "All MSIX files found:" + Get-ChildItem "." -Recurse -Filter "*.msix" | ForEach-Object { Write-Host $_.FullName } + $x64 = Get-ChildItem "." -Recurse -Filter "*.msix" | Where-Object { $_.Name -match "x64" } | Select-Object -First 1 + $arm64 = Get-ChildItem "." -Recurse -Filter "*.msix" | Where-Object { $_.Name -match "arm64" } | Select-Object -First 1 + if (-not $x64) { throw "x64 MSIX not found" } + if (-not $arm64) { throw "ARM64 MSIX not found" } + echo "X64_PATH=$($x64.FullName)" >> $env:GITHUB_OUTPUT + echo "ARM64_PATH=$($arm64.FullName)" >> $env:GITHUB_OUTPUT + Write-Host "x64: $($x64.FullName)" + Write-Host "arm64: $($arm64.FullName)" + shell: pwsh + + - name: Create bundle mapping + run: | + Set-Location "${{ env.FOLDER_NAME }}" + $x64Rel = (Resolve-Path -Relative "${{ steps.msix.outputs.X64_PATH }}") + $arm64Rel = (Resolve-Path -Relative "${{ steps.msix.outputs.ARM64_PATH }}") + $bundleName = "${{ env.EXTENSION_NAME }}_${{ steps.version.outputs.VERSION }}_x64.msix" + $bundleArm = "${{ env.EXTENSION_NAME }}_${{ steps.version.outputs.VERSION }}_arm64.msix" + $content = "[Files]`n`"$x64Rel`" `"$bundleName`"`n`"$arm64Rel`" `"$bundleArm`"" + $content | Set-Content bundle_mapping.txt + Get-Content bundle_mapping.txt + shell: pwsh + + - name: Find makeappx + id: makeappx + run: | + $arch = switch ($env:PROCESSOR_ARCHITECTURE) { "AMD64" { "x64" } "ARM64" { "arm64" } default { "x64" } } + $found = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\$arch\makeappx.exe" -ErrorAction SilentlyContinue ` + | Sort-Object FullName -Descending | Select-Object -First 1 + if (-not $found) { throw "makeappx.exe not found" } + echo "PATH=$($found.FullName)" >> $env:GITHUB_OUTPUT + Write-Host "makeappx: $($found.FullName)" + shell: pwsh + + - name: Bundle x64 + ARM64 into msixbundle + run: | + Set-Location "${{ env.FOLDER_NAME }}" + $bundle = "${{ env.EXTENSION_NAME }}_${{ steps.version.outputs.VERSION }}_Bundle.msixbundle" + & "${{ steps.makeappx.outputs.PATH }}" bundle /v /f bundle_mapping.txt /p $bundle + echo "BUNDLE_PATH=${{ env.FOLDER_NAME }}\$bundle" >> $env:GITHUB_ENV + shell: pwsh + + - name: Decode and import signing certificate + run: | + $pfxBytes = [Convert]::FromBase64String("${{ secrets.SIGNING_CERT_PFX }}") + $pfxPath = "$env:RUNNER_TEMP\signing.pfx" + [IO.File]::WriteAllBytes($pfxPath, $pfxBytes) + echo "PFX_PATH=$pfxPath" >> $env:GITHUB_ENV + shell: pwsh + + - name: Sign msixbundle + run: | + $signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" -ErrorAction SilentlyContinue ` + | Sort-Object Name -Descending | Select-Object -First 1 + if (-not $signtool) { throw "signtool.exe not found" } + & $signtool sign /fd SHA256 /p "${{ secrets.SIGNING_CERT_PASSWORD }}" /f "$env:PFX_PATH" "$env:BUNDLE_PATH" + shell: pwsh + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.version.outputs.VERSION }} + name: "${{ env.DISPLAY_NAME }} v${{ steps.version.outputs.VERSION }}" + body: | + ## ${{ env.DISPLAY_NAME }} v${{ steps.version.outputs.VERSION }} + + ## What's New + ${{ github.event.inputs.release_notes }} + + ## Installation + + Download and double-click `${{ env.EXTENSION_NAME }}_${{ steps.version.outputs.VERSION }}_Bundle.msixbundle`. + + > **Note:** The package is self-signed. If Windows blocks installation, right-click the `.msixbundle` → Properties → Digital Signatures → install the certificate to "Trusted People" first. + + Alternatively, install via PowerShell: + ```powershell + Add-AppxPackage .\${{ env.EXTENSION_NAME }}_${{ steps.version.outputs.VERSION }}_Bundle.msixbundle + ``` + files: ${{ env.FOLDER_NAME }}/${{ env.EXTENSION_NAME }}_${{ steps.version.outputs.VERSION }}_Bundle.msixbundle + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/AgentsPanelExtension/AgentsPanelExtension.csproj b/AgentsPanelExtension/AgentsPanelExtension.csproj index afbda5e..8bf07ce 100644 --- a/AgentsPanelExtension/AgentsPanelExtension.csproj +++ b/AgentsPanelExtension/AgentsPanelExtension.csproj @@ -13,6 +13,12 @@ CostaFotiadis.AgentsPanelforCommandPalette CN=3D57AA92-97A9-42D2-8CB0-4207D9145514 0.1.0.0 + + $(AppxPackageVersion) + Costa Fotiadis + Agents Panel for Command Palette + © Costa Fotiadis true enable @@ -50,7 +56,7 @@ - + diff --git a/AgentsPanelExtension/Data/Claude/ClaudeUsageProvider.cs b/AgentsPanelExtension/Data/Claude/ClaudeUsageProvider.cs index 8c1493a..0502de5 100644 --- a/AgentsPanelExtension/Data/Claude/ClaudeUsageProvider.cs +++ b/AgentsPanelExtension/Data/Claude/ClaudeUsageProvider.cs @@ -30,9 +30,6 @@ internal sealed class ClaudeUsageProvider : IAgentUsageProvider // The beta header the endpoint requires alongside a consumer OAuth token. private const string AnthropicBeta = "oauth-2025-04-20"; - // Honest self-identification (deliberate decision: no client spoofing). One place to bump. - private const string UserAgent = "agents-panel/0.1.0"; - // One client for the process. Headers that never change ride on it; the Authorization header is // per-request because the token is re-read from disk on every fetch. private static readonly HttpClient Http = CreateClient(); @@ -117,7 +114,7 @@ public async Task GetUsageAsync(CancellationToken ct = defa private static HttpClient CreateClient() { var client = new HttpClient(); - client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", UserAgent); + client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", AppInfo.UserAgent); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); return client; } diff --git a/AgentsPanelExtension/Data/Codex/CodexUsageProvider.cs b/AgentsPanelExtension/Data/Codex/CodexUsageProvider.cs index d090172..49425ad 100644 --- a/AgentsPanelExtension/Data/Codex/CodexUsageProvider.cs +++ b/AgentsPanelExtension/Data/Codex/CodexUsageProvider.cs @@ -30,9 +30,6 @@ internal sealed class CodexUsageProvider : IAgentUsageProvider // style is for API-key auth, which this provider doesn't use. private const string UsageEndpoint = "https://chatgpt.com/backend-api/wham/usage"; - // Honest self-identification (deliberate decision: no client spoofing). One place to bump. - private const string UserAgent = "agents-panel/0.1.0"; - // One client for the process. Headers that never change ride on it; the Authorization and // account-id headers are per-request because credentials are re-read from disk on every fetch. private static readonly HttpClient Http = CreateClient(); @@ -122,7 +119,7 @@ public async Task GetUsageAsync(CancellationToken ct = defa private static HttpClient CreateClient() { var client = new HttpClient(); - client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", UserAgent); + client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", AppInfo.UserAgent); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); return client; } diff --git a/AgentsPanelExtension/Data/Copilot/CopilotUsageProvider.cs b/AgentsPanelExtension/Data/Copilot/CopilotUsageProvider.cs index 9325552..6ab855d 100644 --- a/AgentsPanelExtension/Data/Copilot/CopilotUsageProvider.cs +++ b/AgentsPanelExtension/Data/Copilot/CopilotUsageProvider.cs @@ -30,9 +30,8 @@ internal sealed class CopilotUsageProvider : IAgentUsageProvider private const string UsageEndpoint = "https://api.github.com/copilot_internal/user"; - // Honest self-identification (deliberate decision: no client spoofing — no Editor-Version - // masquerade either; the endpoint answers plain user agents). One place to bump. - private const string UserAgent = "agents-panel/0.1.0"; + // Deliberate decision for this endpoint: no Editor-Version masquerade either — it answers + // plain user agents (AppInfo.UserAgent). // One client for the process. Headers that never change ride on it; Authorization is // per-request because the token is re-discovered on every fetch. @@ -112,7 +111,7 @@ public async Task GetUsageAsync(CancellationToken ct = defa private static HttpClient CreateClient() { var client = new HttpClient(); - client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", UserAgent); + client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", AppInfo.UserAgent); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); return client; } diff --git a/AgentsPanelExtension/Helpers/AppInfo.cs b/AgentsPanelExtension/Helpers/AppInfo.cs new file mode 100644 index 0000000..2d44876 --- /dev/null +++ b/AgentsPanelExtension/Helpers/AppInfo.cs @@ -0,0 +1,16 @@ +using System; + +namespace AgentsPanelExtension; + +// The one honest self-identification string every provider HTTP client sends (deliberate decision: +// no client spoofing, no Editor-Version masquerades). The version rides in from the assembly — +// csproj mirrors , so a release bumps the csproj once and the UA +// follows automatically. Never hardcode a version literal here or in a provider. +internal static class AppInfo +{ + // Three-part ("agents-panel/0.1.0"), matching the historical literal the endpoints have seen. + public static readonly string UserAgent = + typeof(AppInfo).Assembly.GetName().Version is { } v + ? $"agents-panel/{v.Major}.{v.Minor}.{v.Build}" + : "agents-panel"; +} diff --git a/AgentsPanelExtension/Helpers/UsageStatusHint.cs b/AgentsPanelExtension/Helpers/UsageStatusHint.cs index 2458fd1..410d2b6 100644 --- a/AgentsPanelExtension/Helpers/UsageStatusHint.cs +++ b/AgentsPanelExtension/Helpers/UsageStatusHint.cs @@ -62,8 +62,10 @@ internal static class UsageStatusHint } // Status rows are informational — there is nothing useful for Enter to do (we can't sign the user - // in), so a NoOpCommand with a stable non-empty Id. Subtitle and pill come from the per-status - // helpers above so the text/colors stay defined once. + // in), so a NoOpCommand with a stable non-empty Id. Decision (2026-08-14): keep it that way — no + // deep-links, no third-party sign-in guidance; the row states the fact and the user handles their + // own agent apps. Subtitle and pill come from the per-status helpers above so the text/colors stay + // defined once. private static ListItem Row(DomainUsageSnapshot snapshot, string title, string kind) => new(new NoOpCommand { Id = $"com.costafotiadis.agentspanel.status.{snapshot.ProviderId}.{kind}" }) { diff --git a/AgentsPanelExtension/Pages/LegalPage.cs b/AgentsPanelExtension/Pages/LegalPage.cs index 5a88e99..4349d06 100644 --- a/AgentsPanelExtension/Pages/LegalPage.cs +++ b/AgentsPanelExtension/Pages/LegalPage.cs @@ -10,52 +10,7 @@ namespace AgentsPanelExtension; // providers, no lifecycle. (Modeled on MarketExtension's DataSourcesPage.) internal sealed partial class LegalPage : ContentPage { - private readonly MarkdownContent _content = new( - """ - # The Legal Bit - - This app is an **independent, third-party tool**. It is **not - affiliated with, endorsed by, or connected to** Anthropic, Claude, - OpenAI, ChatGPT, GitHub, Copilot, or any AI provider. Provider names - and logos belong to their respective owners. - - ## No backend, no tracking - - This app has **no server, no analytics, and no telemetry**. It - collects nothing, stores nothing outside your machine, and shares - nothing with anyone. The only network requests it makes are the usage - checks you see on screen, sent **directly to the provider**. - - ## Your credentials stay yours - - The app reads the sign-in credentials that a provider's own app - (e.g. Claude Code) already keeps on your machine. It never creates, - modifies, uploads, or proxies them — each token is read locally and - sent **only to the provider it belongs to**, and nowhere else. - - Your account and any agreement governing it are **between you and - that provider**. This app is just a surface that displays what the - provider reports, and you are responsible for using your account - within the provider's terms. - - ## No guarantees - - Usage numbers come from provider endpoints that are **not officially - documented for third-party use** — they can be delayed, inaccurate, - or stop working at any time without notice. The app is provided - **as is, without warranty of any kind**. Don't rely on it as the - authoritative record of your usage or billing — the provider's own - app and website are. - - ## What this app does and doesn't do - - **Does:** read your local sign-in, ask the provider for your usage - numbers, and show them to you. - - **Doesn't:** run a server, track you, phone home, refresh or modify - your credentials, or send anything anywhere except the provider's - own API. - """); + private readonly MarkdownContent _content = new(Resources.Legal_Markdown); public LegalPage() { diff --git a/AgentsPanelExtension/Pages/UsageDockPage.cs b/AgentsPanelExtension/Pages/UsageDockPage.cs index b07ce1e..008f7bb 100644 --- a/AgentsPanelExtension/Pages/UsageDockPage.cs +++ b/AgentsPanelExtension/Pages/UsageDockPage.cs @@ -113,7 +113,8 @@ public override IListItem[] GetItems() }); } - // No windows to show — the button IS the status (sign in / token expired / no data). + // No windows to show — the button IS the status. Ok-but-empty is a healthy account that + // reported no active limits; it must NOT borrow the error wording (we reached the API fine). if (usage.Windows.Count == 0) { var (title, subtitle) = usage.Snapshot.Status switch @@ -122,6 +123,9 @@ public override IListItem[] GetItems() Strings.Format(Resources.Dock_SignIn_Subtitle, usage.Snapshot.ProviderDisplayName)), UsageStatus.TokenExpired => (Resources.Dock_Expired_Title, Strings.Format(Resources.Dock_Expired_Subtitle, usage.Snapshot.ProviderDisplayName)), + UsageStatus.RateLimited => (Resources.Dock_RateLimited_Title, + Strings.Format(Resources.Status_RateLimited_Title, usage.Snapshot.ProviderDisplayName)), + UsageStatus.Ok => (Resources.Usage_Empty_Title, Resources.Usage_Empty_Subtitle), _ => (Resources.Usage_Empty_Title, Strings.Format(Resources.Status_Error_Title, usage.Snapshot.ProviderDisplayName)), }; diff --git a/AgentsPanelExtension/Program.cs b/AgentsPanelExtension/Program.cs index 4bd0564..39b35c9 100644 --- a/AgentsPanelExtension/Program.cs +++ b/AgentsPanelExtension/Program.cs @@ -1,3 +1,4 @@ +using AgentsPanelExtension.Properties; using Microsoft.CommandPalette.Extensions; using Shmuelie.WinRTServer; using Shmuelie.WinRTServer.CsWinRT; @@ -43,8 +44,9 @@ public static void Main(string[] args) { _ = MessageBox( IntPtr.Zero, - "Agents Panel for Command Palette is a background extension.\n\nTo use it, open PowerToys Command Palette and search for \"Agents Panel\".", - "Agents Panel for Command Palette", + Strings.Format(Resources.DirectLaunch_Message, + Resources.Extension_DisplayName, Resources.Command_AgentsPanel), + Resources.Extension_DisplayName, 0x40 /* MB_ICONINFORMATION */); } } diff --git a/AgentsPanelExtension/Properties/Resources.Designer.cs b/AgentsPanelExtension/Properties/Resources.Designer.cs index 6c39bf0..80134ce 100644 --- a/AgentsPanelExtension/Properties/Resources.Designer.cs +++ b/AgentsPanelExtension/Properties/Resources.Designer.cs @@ -97,7 +97,32 @@ public static string Page_Legal_Title { } /// - /// Looks up a localized string similar to Use {0} to refresh the sign-in. + /// Looks up a localized string similar to {0} is a background extension. + /// + ///To use it, open PowerToys Command Palette and search for "{1}". + /// + public static string DirectLaunch_Message { + get { + return ResourceManager.GetString("DirectLaunch_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to # The Legal Bit + /// + ///This app is an **independent, third-party tool**. It is **not + ///affiliated with, endorsed by, or connected to** Anthropic, Claude, + ///OpenAI, ChatGPT, GitHub, Copilot, or any AI provider. Provider names + ///and logos belong to their respective owners. [rest of string was truncated]";. + /// + public static string Legal_Markdown { + get { + return ResourceManager.GetString("Legal_Markdown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The saved {0} sign-in has expired. /// public static string Dock_Expired_Subtitle { get { @@ -105,6 +130,15 @@ public static string Dock_Expired_Subtitle { } } + /// + /// Looks up a localized string similar to Rate-limited. + /// + public static string Dock_RateLimited_Title { + get { + return ResourceManager.GetString("Dock_RateLimited_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Token expired. /// @@ -124,7 +158,7 @@ public static string Dock_SignIn_Subtitle { } /// - /// Looks up a localized string similar to Sign in. + /// Looks up a localized string similar to Not signed in. /// public static string Dock_SignIn_Title { get { @@ -205,7 +239,7 @@ public static string Status_Error_Tag { } /// - /// Looks up a localized string similar to Sign in to {0}. + /// Looks up a localized string similar to Not signed in to {0}. /// public static string Status_NotSignedIn_Title { get { @@ -214,7 +248,7 @@ public static string Status_NotSignedIn_Title { } /// - /// Looks up a localized string similar to No local sign-in found — log in via the agent's own app. + /// Looks up a localized string similar to No sign-in found on this PC. /// public static string Status_NotSignedIn_Subtitle { get { @@ -268,7 +302,7 @@ public static string Status_TokenExpired_Title { } /// - /// Looks up a localized string similar to Just use the agent — its own app refreshes the sign-in. + /// Looks up a localized string similar to The saved sign-in has expired. /// public static string Status_TokenExpired_Subtitle { get { @@ -439,7 +473,7 @@ public static string Settings_Refresh_Label { } /// - /// Looks up a localized string similar to Show the extra-usage credits balance when enabled on the account. + /// Looks up a localized string similar to Show pay-as-you-go extra usage when the account reports it. /// public static string Settings_ShowExtraUsage_Desc { get { @@ -457,7 +491,7 @@ public static string Settings_ShowExtraUsage_Label { } /// - /// Looks up a localized string similar to Show the per-model weekly limits (Opus, Sonnet) as their own rows. + /// Looks up a localized string similar to Show model-specific limits (e.g. Opus, Sonnet) as their own rows. /// public static string Settings_ShowModelWindows_Desc { get { diff --git a/AgentsPanelExtension/Properties/Resources.resx b/AgentsPanelExtension/Properties/Resources.resx index 93fff05..541aa74 100644 --- a/AgentsPanelExtension/Properties/Resources.resx +++ b/AgentsPanelExtension/Properties/Resources.resx @@ -131,10 +131,10 @@ The account reported no active limits - Sign in to {0} + Not signed in to {0} - No local sign-in found — log in via the agent's own app + No sign-in found on this PC Not signed in @@ -143,7 +143,7 @@ {0} sign-in expired - Just use the agent — its own app refreshes the sign-in + The saved sign-in has expired Expired @@ -167,7 +167,7 @@ Error - Sign in + Not signed in {0} login not found @@ -176,7 +176,60 @@ Token expired - Use {0} to refresh the sign-in + The saved {0} sign-in has expired + + + Rate-limited + + + {0} is a background extension. + +To use it, open PowerToys Command Palette and search for "{1}". + + + # The Legal Bit + +This app is an **independent, third-party tool**. It is **not +affiliated with, endorsed by, or connected to** Anthropic, Claude, +OpenAI, ChatGPT, GitHub, Copilot, or any AI provider. Provider names +and logos belong to their respective owners. + +## No backend, no tracking + +This app has **no server, no analytics, and no telemetry**. It +collects nothing, stores nothing outside your machine, and shares +nothing with anyone. The only network requests it makes are the usage +checks you see on screen, sent **directly to the provider**. + +## Your credentials stay yours + +The app reads the sign-in credentials that a provider's own app +(e.g. Claude Code) already keeps on your machine. It never creates, +modifies, uploads, or proxies them — each token is read locally and +sent **only to the provider it belongs to**, and nowhere else. + +Your account and any agreement governing it are **between you and +that provider**. This app is just a surface that displays what the +provider reports, and you are responsible for using your account +within the provider's terms. + +## No guarantees + +Usage numbers come from provider endpoints that are **not officially +documented for third-party use** — they can be delayed, inaccurate, +or stop working at any time without notice. The app is provided +**as is, without warranty of any kind**. Don't rely on it as the +authoritative record of your usage or billing — the provider's own +app and website are. + +## What this app does and doesn't do + +**Does:** read your local sign-in, ask the provider for your usage +numbers, and show them to you. + +**Doesn't:** run a server, track you, phone home, refresh or modify +your credentials, or send anything anywhere except the provider's +own API. Refresh interval @@ -188,13 +241,13 @@ Per-model weekly limits - Show the per-model weekly limits (Opus, Sonnet) as their own rows + Show model-specific limits (e.g. Opus, Sonnet) as their own rows Extra usage - Show the extra-usage credits balance when enabled on the account + Show pay-as-you-go extra usage when the account reports it Copilot GitHub token diff --git a/AgentsPanelExtension/create-signing-cert.ps1 b/AgentsPanelExtension/create-signing-cert.ps1 new file mode 100644 index 0000000..fa082ac --- /dev/null +++ b/AgentsPanelExtension/create-signing-cert.ps1 @@ -0,0 +1,53 @@ +#Requires -RunAsAdministrator +<# +.SYNOPSIS + Generates a self-signed code signing certificate for MSIX sideload releases. + +.DESCRIPTION + Creates a certificate matching the Publisher in Package.appxmanifest, + exports it as a PFX, and copies the base64-encoded value to the clipboard + ready to paste as the SIGNING_CERT_PFX GitHub secret. + +.PARAMETER Password + Password to protect the PFX file. Also goes into the SIGNING_CERT_PASSWORD secret. + +.PARAMETER OutFile + Path to write the PFX file. Defaults to signing.pfx next to this script. + +.EXAMPLE + .\create-signing-cert.ps1 -Password "hunter2" +#> +param( + [Parameter(Mandatory)] + [string]$Password, + + [string]$OutFile = "$PSScriptRoot\signing.pfx" +) + +# Must match Identity/Publisher in Package.appxmanifest +$publisher = "CN=3D57AA92-97A9-42D2-8CB0-4207D9145514" + +Write-Host "Creating certificate for: $publisher" + +$cert = New-SelfSignedCertificate ` + -Type Custom ` + -Subject $publisher ` + -KeyUsage DigitalSignature ` + -FriendlyName "Agents Panel Extension Signing" ` + -CertStoreLocation "Cert:\CurrentUser\My" ` + -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") + +$securePassword = ConvertTo-SecureString -String $Password -Force -AsPlainText +Export-PfxCertificate -Cert $cert -FilePath $OutFile -Password $securePassword | Out-Null + +Write-Host "PFX written to: $OutFile" +Write-Host "" +Write-Host "GitHub secrets to set:" +Write-Host " SIGNING_CERT_PASSWORD = $Password" +Write-Host "" + +$base64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($OutFile)) +$base64 | Set-Clipboard +Write-Host " SIGNING_CERT_PFX = (copied to clipboard)" +Write-Host "" +Write-Host "Keep $OutFile safe - you need it to re-sign future releases with the same identity." diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6fc77a3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Costa Fotiadis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e34b555..12ed970 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ usefully, from the **Command Palette Dock**. (chat / completions / premium requests, per plan). - **Extensible by design**: providers implement a small `IAgentUsageProvider` interface; more agents can slot in later. -- **Demo mode**: built-in sample data to try the UI with no account at all. - Stale-tolerant: if a refresh fails (offline, rate-limited), the last known numbers stay visible and are marked stale instead of vanishing. @@ -46,7 +45,8 @@ usefully, from the **Command Palette Dock**. ## Building ``` -dotnet build AgentsPanelExtension.sln +dotnet build AgentsPanelExtension.sln -p:Platform=x64 ``` -Deploy the MSIX from Visual Studio (the "(Package)" launch profile), then reload Command Palette. +Deploy the MSIX package from your IDE (Rider or Visual Studio's "(Package)" launch profile), then +reload Command Palette. diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..6b840e0 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,44 @@ + + + + + + Agents Panel for Command Palette + + + +
+ Agents Panel + +
+ +

Agents Panel for Command Palette

+

Your AI agents' usage limits — right inside Windows PowerToys Command Palette.

+ +

Agents Panel is an open-source PowerToys Command Palette extension that shows how much of your Claude, Codex, and GitHub Copilot usage quotas you have used — session and weekly percentages, reset times, and plan info, with pinnable Dock quick-look buttons. It reads the sign-ins your machine already has and runs entirely locally — no telemetry, no analytics, no accounts of its own.

+ + + +
+

Questions? Open an issue.

+

Agents Panel is not affiliated with, endorsed by, or sponsored by Anthropic, OpenAI, GitHub, or Microsoft.

+
+ + diff --git a/docs/privacy.html b/docs/privacy.html new file mode 100644 index 0000000..2c0e528 --- /dev/null +++ b/docs/privacy.html @@ -0,0 +1,74 @@ + + + + + + Privacy Policy — Agents Panel for Command Palette + + + +
+ Agents Panel + +
+ +

Privacy Policy

+

Agents Panel for Command Palette — Last updated August 2026

+ +
+ In short: Agents Panel runs entirely on your machine. It has no telemetry, no analytics, and no accounts of its own, and it never sends your data to its author. It reads the sign-in credentials that other apps on your machine already store (Claude Code, the Codex CLI/app, GitHub tooling) and uses them for exactly one thing: asking each provider's own usage endpoint for your quota numbers, directly from your device. +
+ +

Overview

+

Agents Panel for Command Palette (the "extension") is an open-source PowerToys Command Palette extension that shows the usage quotas of AI coding agents — Claude, Codex, and GitHub Copilot. This policy explains what the extension reads, what it stores, what leaves your device, and what it deliberately does not do.

+ +

Credentials the extension reads (but does not own)

+

To fetch your usage numbers, the extension reads access tokens that other applications on your machine have already stored as part of their own sign-in:

+
    +
  • Claude: the Claude Code sign-in token from %USERPROFILE%\.claude\.credentials.json.
  • +
  • Codex: the Codex CLI/app sign-in from auth.json in your Codex home directory.
  • +
  • GitHub Copilot: an existing GitHub token found via environment variables, the Windows Credential Manager (GitHub CLI or Copilot CLI sign-ins), GitHub CLI's hosts.yml, or the editor Copilot apps' local files — or a fine-grained personal access token you paste into the extension's settings yourself.
  • +
+

Tokens are read at request time only. The extension never copies them elsewhere, never logs them, never refreshes or modifies them, and never transmits them to anyone except the matching provider's own API (see below). One exception on storage: if you paste a GitHub personal access token into the extension's settings, that token is saved in plain text in the extension's local settings file (agentspanel.settings.json under Command Palette's settings folder) — use a fine-grained token limited to Copilot Requests: Read, and remove it from settings to delete it.

+ +

Data stored on your device

+

The extension keeps a small local settings file (refresh interval, display options, and the optional GitHub token described above). It stays on your machine and is never transmitted to the author. You can clear it by removing the settings file or uninstalling the extension.

+ +

Data that leaves your device

+

To show your quotas, the extension makes periodic requests over the internet, each carrying the relevant token, straight from your machine to the provider that issued that token:

+
    +
  • Anthropic (api.anthropic.com) — your Claude usage and limits.
  • +
  • OpenAI (chatgpt.com) — your Codex/ChatGPT usage and limits.
  • +
  • GitHub (api.github.com) — your Copilot quotas and plan.
  • +
+

These requests do not pass through any server operated by the author, and the author never sees your tokens, your account details, or your usage numbers. Each provider handles the requests it receives under its own privacy policy and terms of service; your use of those services remains subject to their terms.

+ +

What the extension does not do

+

The extension contains no telemetry channel of any kind. It does not:

+
    +
  • Collect analytics or usage statistics
  • +
  • Send crash reports or diagnostics
  • +
  • Require or create an account
  • +
  • Store, log, refresh, or forward your tokens (beyond the optional pasted GitHub token saved in local settings, described above)
  • +
  • Share, sell, or transmit your data to the author or any party other than the three providers described above
  • +
+

Release builds emit nothing off your machine beyond the direct provider requests above.

+ +

Children's privacy

+

The extension is a general-purpose developer tool and is not directed at children. It does not knowingly collect any information from children.

+ +

Changes to this policy

+

This policy may be updated from time to time. The "Last updated" date above reflects the latest revision; continued use of the extension after a change constitutes acceptance of the revised policy.

+ +

Contact

+

Questions? Open an issue on GitHub.

+ + + + diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..4f46099 --- /dev/null +++ b/docs/style.css @@ -0,0 +1,80 @@ +:root { + --bg: #ffffff; + --fg: #1a1a1a; + --muted: #666666; + --link: #0066cc; + --border: #e5e5e5; + --card: #fafafa; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #15171a; + --fg: #e8e8e8; + --muted: #9aa0a6; + --link: #5aa9ff; + --border: #2a2d31; + --card: #1c1f23; + } +} + +* { box-sizing: border-box; } + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + max-width: 720px; + margin: 0 auto; + padding: 60px 24px 80px; + color: var(--fg); + background: var(--bg); + line-height: 1.7; +} + +a { color: var(--link); text-decoration: none; } +a:hover { text-decoration: underline; } + +header.site { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 40px; + padding-bottom: 20px; + border-bottom: 1px solid var(--border); +} +header.site .name { font-weight: 600; font-size: 1.05rem; } +header.site nav { margin-left: auto; display: flex; gap: 18px; font-size: 0.95rem; } + +h1 { font-size: 1.9rem; margin-bottom: 4px; } +h2 { font-size: 1.15rem; margin-top: 36px; } +.subtitle { color: var(--muted); margin-bottom: 40px; } + +ul { padding-left: 22px; } +li { margin: 6px 0; } + +.cards { display: grid; gap: 16px; margin-top: 32px; } +.card { + display: block; + border: 1px solid var(--border); + background: var(--card); + border-radius: 10px; + padding: 18px 20px; +} +.card:hover { border-color: var(--link); text-decoration: none; } +.card .title { font-weight: 600; font-size: 1.05rem; color: var(--fg); } +.card .desc { color: var(--muted); font-size: 0.95rem; margin-top: 4px; } + +footer.site { + margin-top: 60px; + padding-top: 20px; + border-top: 1px solid var(--border); + color: var(--muted); + font-size: 0.9rem; +} + +.disclaimer { + border-left: 3px solid var(--link); + background: var(--card); + padding: 12px 16px; + border-radius: 0 8px 8px 0; + margin: 24px 0; +} diff --git a/docs/terms.html b/docs/terms.html new file mode 100644 index 0000000..2420bdf --- /dev/null +++ b/docs/terms.html @@ -0,0 +1,60 @@ + + + + + + Terms & Conditions — Agents Panel for Command Palette + + + +
+ Agents Panel + +
+ +

Terms & Conditions

+

Agents Panel for Command Palette — Last updated August 2026

+ +
+ Unofficial, best-effort numbers. Agents Panel is an independent, unofficial tool. The usage figures it displays come from endpoints the providers have not documented for third-party use; they may be delayed, inaccurate, or incomplete, and any provider may change or remove them at any time, at which point the extension may stop showing data. The numbers shown by each provider's own apps are always authoritative. +
+ +

1. Acceptance

+

By installing or using Agents Panel for Command Palette (the "extension"), you agree to these Terms & Conditions. If you do not agree, do not use the extension.

+ +

2. License

+

The extension is free and open-source software, licensed under the MIT License. Your use, modification, and distribution of the source code are governed by that license. The source is available at github.com/CostaFot/agents-panel-extension.

+ +

3. Non-affiliation

+

Agents Panel is an independent project. It is not affiliated with, endorsed by, sponsored by, or supported by Anthropic, OpenAI, GitHub, or Microsoft. "Claude" is a trademark of Anthropic, PBC; "ChatGPT" and "Codex" are trademarks of OpenAI; "GitHub" and "GitHub Copilot" are trademarks of GitHub, Inc.; "Windows", "PowerToys", and "Microsoft" are trademarks of Microsoft Corporation. Those names are used solely to identify the services whose usage the extension displays.

+ +

4. Third-party services & your accounts

+

The extension displays usage data for subscriptions and accounts you hold with third-party providers (Anthropic, OpenAI, GitHub). It requires those existing sign-ins to show live data and provides no service of its own without them. Your relationship with each provider — including your subscription, its limits, and your compliance with its terms of service — is solely between you and that provider. The extension reads locally stored credentials created by those providers' own apps and uses them only as described in the Privacy Policy; you are responsible for ensuring that this use is acceptable under your agreements with each provider.

+ +

5. Data accuracy & availability

+

The usage endpoints the extension calls are not documented or sanctioned for third-party use. Figures may lag the provider's own display, be shaped differently across plans, or disappear entirely when a provider changes its API. The extension deliberately degrades by showing last-known values marked as stale, or a sign-in/error row, rather than guessing. Do not rely on the extension as your sole indicator of remaining quota.

+ +

6. No warranty

+

The extension is provided "as is" and "as available", without warranties of any kind, express or implied, including but not limited to merchantability, fitness for a particular purpose, accuracy, and non-infringement. The author does not warrant that the extension or its data will be uninterrupted, error-free, accurate, or current.

+ +

7. Limitation of liability

+

To the maximum extent permitted by law, the author shall not be liable for any direct, indirect, incidental, special, consequential, or exemplary damages — including, without limitation, exhausted usage quotas, interrupted work, account or subscription issues with any provider, loss of profits, or loss of data — arising out of or relating to your use of, or inability to use, the extension or the data it displays, even if advised of the possibility of such damages.

+ +

8. Trademarks

+

All company names, product names, logos, and trademarks shown in or referenced by the extension are the property of their respective owners and are used for identification purposes only. Their appearance does not imply any affiliation with or endorsement by those owners.

+ +

9. Changes to these terms

+

These terms may be updated from time to time. The "Last updated" date above reflects the latest revision; continued use of the extension after a change constitutes acceptance of the revised terms.

+ +

10. Contact

+

Questions? Open an issue on GitHub.

+ + + + diff --git a/notes/certification-notes.md b/notes/certification-notes.md new file mode 100644 index 0000000..380b4e3 --- /dev/null +++ b/notes/certification-notes.md @@ -0,0 +1,48 @@ +# Certification test notes (Partner Center submission) + +Paste (or adapt) the block below into the "Notes for certification" field. A Store reviewer has no +Claude/Codex/Copilot credentials, so the app will show three "Not signed in" rows — these notes explain +why that is the expected, fully functional state, and give the reviewer a way to exercise live data. + +--- + +**What this app is.** Agents Panel is a PowerToys Command Palette extension (it requires Microsoft +PowerToys with Command Palette to be installed and running). It displays the usage quotas of AI +coding agents the user already subscribes to — Claude, Codex (ChatGPT), and GitHub Copilot — by +reading the sign-in credentials those providers' own apps store locally on the machine and querying +each provider's usage API directly. The app has no accounts, servers, or telemetry of its own. + +**How to launch it.** Install Microsoft PowerToys, enable Command Palette, install this package, +then open Command Palette (default Win+Alt+Space) and run "Agents Panel". Each provider also +exposes a dock band that can be pinned via Command Palette's dock. + +**What you will see without any AI-agent credentials.** One row per provider (Claude, Codex, +GitHub Copilot), each reading "Not signed in to — No sign-in found on this PC". This is +the designed behavior for a machine that has no AI-agent subscriptions — the app +deliberately does not ship fake data. All navigation, settings, refresh, and dock-pinning behavior +works in this state. + +**How to see live data (optional, Copilot is the easiest).** In the app's settings (Command +Palette → Agents Panel → Settings), paste a GitHub fine-grained personal access token that has the +account permission "Copilot Requests: Read" for any GitHub account with Copilot enabled (the free +Copilot tier is sufficient). The Copilot row will then display live monthly quota data. Claude and +Codex data appear only when Claude Code or the Codex CLI/app is signed in on the machine. + +**Why runFullTrust.** The app is a Command Palette extension: it runs as an out-of-process COM +server that the PowerToys Command Palette host activates, which requires the full-trust +application capability (standard for all Command Palette extensions). + +**Privacy.** Tokens are read at request time only and sent only to the provider that issued them +(api.anthropic.com, chatgpt.com, api.github.com). No telemetry. Privacy policy: +https://costafot.github.io/agents-panel-extension/privacy.html + +--- + +Notes to self (not for the reviewer): + +- The privacy URL above assumes GitHub Pages is enabled on the repo (B5); verify it resolves + before submitting. +- If certification pushes back on the "Not signed in" rows anyway, the fallback argument: the Store + policy concern is apps that are non-functional without undisclosed purchases — the listing + discloses the subscription requirement (B8) and the Copilot path above gives the reviewer a + zero-cost way to see live data (free Copilot tier). diff --git a/notes/releasing.md b/notes/releasing.md index 83d63ef..2bf5bdd 100644 --- a/notes/releasing.md +++ b/notes/releasing.md @@ -6,20 +6,18 @@ infrastructure lands (see `notes/store-readiness.md` for the full checklist). Cu ## Version bump — all sites together, one dedicated commit -Six sites today (D14 in the checklist will consolidate the three UserAgent constants into one, -shrinking this table): +Three sites (D14 done 2026-08-14: the HTTP `User-Agent` is derived at runtime from the assembly +version — csproj `` mirrors ``, see `Helpers/AppInfo.cs` — so no code +files carry a version literal anymore): | File | Field | |---|---| -| `AgentsPanelExtension/AgentsPanelExtension.csproj` | `` | +| `AgentsPanelExtension/AgentsPanelExtension.csproj` | `` (`` follows it automatically) | | `AgentsPanelExtension/Package.appxmanifest` | `Identity Version=` | | `AgentsPanelExtension/app.manifest` | `assemblyIdentity version=` | -| `AgentsPanelExtension/Data/Claude/ClaudeUsageProvider.cs` | `UserAgent` (`agents-panel/`, 3-part) | -| `AgentsPanelExtension/Data/Codex/CodexUsageProvider.cs` | `UserAgent` | -| `AgentsPanelExtension/Data/Copilot/CopilotUsageProvider.cs` | `UserAgent` | -MSIX wants 4-part `Major.Minor.Build.Revision`; the UA strings carry the 3-part form. Bump -one-liner (adjust the UA separately — different format): +MSIX wants 4-part `Major.Minor.Build.Revision`; the UA renders the 3-part form of the same number +(`agents-panel/0.1.0`) on its own. Bump one-liner: ```powershell $files = @("AgentsPanelExtension/AgentsPanelExtension.csproj", diff --git a/notes/store-listing.md b/notes/store-listing.md new file mode 100644 index 0000000..b7d600b --- /dev/null +++ b/notes/store-listing.md @@ -0,0 +1,47 @@ +# Microsoft Store listing copy + +Compliance-reviewed copy for the Partner Center submission. Deliberately avoids: no +"official" anywhere, no "real-time"/"live" guarantees (the endpoints are undocumented and can lag +or vanish), no implied affiliation with Anthropic/OpenAI/GitHub/Microsoft, nominative trademark +use only — extra care since all three data endpoints are ToS-gray; required disclosures included +(third-party subscriptions needed, unofficial data source). + +## Short description (search-results summary) + +See your Claude, Codex, and GitHub Copilot usage limits inside PowerToys Command Palette — session and weekly quotas, reset times, and pinnable dock buttons. + +## Description (main listing field) + +**Agents Panel for Command Palette** + +Keep an eye on your AI coding agents' usage quotas right inside Microsoft PowerToys Command Palette — how much of your session and weekly limits you've used, when each window resets, and your plan, without switching apps. + +• One hub with a row per agent — Claude, Codex, and GitHub Copilot — each showing a usage summary at a glance +• Drill into any agent for every quota window, reset times, and plan details +• Pin per-agent dock bands for quick-look buttons like "5h 23%" and "Wk 41%" that update while pinned +• Uses the sign-ins your machine already has (Claude Code, the Codex CLI or app, GitHub tooling) — or paste a GitHub fine-grained token for Copilot +• If a refresh fails, the last known numbers stay visible and are marked stale instead of vanishing +• No accounts of its own, no telemetry — it runs entirely on your machine and talks only to each provider's own API + +This app displays usage for subscriptions you already hold. It requires an active sign-in or subscription with the respective provider — a Claude plan (via Claude Code), a ChatGPT plan (via Codex), or GitHub Copilot — to show live data; without one, an agent's row simply offers sign-in guidance. Your tokens stay on your device and are sent only to the provider that issued them. + +**Independent project.** Agents Panel is an open-source, independent extension. It is not affiliated with, endorsed by, or sponsored by Anthropic, OpenAI, GitHub, or Microsoft. Claude, ChatGPT, Codex, GitHub Copilot, PowerToys, and all other product names and logos are the property of their respective owners and are used only to identify the services whose usage the app displays. + +**Disclaimer.** Usage figures come from endpoints the providers do not document for third-party use; they may be delayed, inaccurate, or incomplete, and a provider may change or remove them at any time, at which point the app may stop showing data for that agent. The numbers shown in each provider's own apps are authoritative — do not rely on this app as your only indicator of remaining quota. + +## Compliance notes + +- **No "official"** — anywhere, ever; the whole listing leans on "independent"/"unofficial". +- **No "real-time"/"live" guarantees** — polling is minutes-coarse and the endpoints are + undocumented; "update while pinned" and the delay disclaimer carry the honest version. +- **Third-party account disclosure** — Store policy requires disclosing that third-party + subscriptions/sign-ins are needed; the "requires an active sign-in or subscription" paragraph + covers it explicitly for all three providers. +- **Undocumented-endpoint disclaimer** — explicit, including that data can stop working entirely; + this also pre-answers a certification question about what happens without credentials (sign-in + guidance rows, see the certification notes in `notes/certification-notes.md`). +- **Trademarks** — nominative use only ("for Command Palette", "your Claude … usage"), plus the + explicit non-affiliation + owners'-rights paragraph naming all four companies. +- **Never claim token safety beyond what's true** — the Copilot PAT pasted in settings is stored + in plain text locally; the listing says only "stay on your device", which is accurate, and the + privacy policy discloses the plaintext detail. diff --git a/notes/store-readiness.md b/notes/store-readiness.md index 10af2c2..3445990 100644 --- a/notes/store-readiness.md +++ b/notes/store-readiness.md @@ -38,66 +38,74 @@ manifest description written, minimal capabilities, GitHub remote configured. ## B. Certification blockers — content & legal -- [ ] **B5 [agent] Hosted privacy policy + terms.** Partner Center requires a privacy-policy URL. +- [x] **B5 [agent] Hosted privacy policy + terms.** (docs/ + deploy-pages.yml written 2026-08-14; **[user] still to do: enable GitHub Pages on the repo** — Settings → Pages → Source: GitHub Actions) Partner Center requires a privacy-policy URL. Create `docs/{index,privacy,terms}.html` + `style.css` modeled on MarketExtension's `docs/`, adapted to this app: credentials read locally from other apps' stores, requests go directly to Anthropic/OpenAI/GitHub, zero telemetry, Copilot PAT stored plaintext in settings JSON. Copy `deploy-pages.yml`; **[user]** enables GitHub Pages on the repo. -- [ ] **B6 [agent] LICENSE.** Add MIT (matching MarketExtension). Without it the public repo is +- [x] **B6 [agent] LICENSE.** Add MIT (matching MarketExtension). Without it the public repo is all-rights-reserved by default. -- [ ] **B7 [agent] README fixes.** Remove the false "Demo mode" claim (line 24 — decided: not +- [x] **B7 [agent] README fixes.** (demo-mode claim removed, build/deploy section fixed 2026-08-14; badges/screenshots deferred to post-approval) Remove the false "Demo mode" claim (line 24 — decided: not implementing it); fix "Deploy the MSIX from Visual Studio" (line 52) vs the Rider reality. Later (after Store approval): release/downloads badges, Store badge (`https://apps.microsoft.com/detail/`), winget one-liner, screenshots — copy MarketExtension's README structure. -- [ ] **B8 [either] Store listing copy → `notes/store-listing.md`.** Short + full description, +- [x] **B8 [either] Store listing copy → `notes/store-listing.md`.** (drafted 2026-08-14 — user reviews before Partner Center paste) Short + full description, **non-affiliation paragraph** (not affiliated with/endorsed by Anthropic, OpenAI, GitHub, or Microsoft), disclose that the app requires third-party subscriptions/sign-ins (Store policy), disclaimer that data comes from undocumented endpoints and may stop working or be inaccurate. Never overclaim: no "official", no "real-time". Trademark use nominative only — extra care since all three endpoints are ToS-gray. -- [ ] **B9 [either] Certification test notes.** A Store reviewer has no Claude/Codex/Copilot +- [x] **B9 [either] Certification test notes.** (drafted 2026-08-14 → `notes/certification-notes.md`) A Store reviewer has no Claude/Codex/Copilot credentials, so they'll see three "Sign in" rows. Write submission notes explaining what the app does, why those rows appear, and how to exercise the UI (e.g. the Copilot PAT setting with a fine-grained test token, if feasible). C10 makes those rows self-explanatory, which helps here. ## C. First-run / UX gaps -- [ ] **C10 [agent] Actionable NotConfigured rows.** Enter currently does nothing on a "Sign in" - row (`UsageStatusHint.cs` NoOpCommand). Minimum: Copilot's row links to the token setting; - Claude/Codex rows get concrete guidance (install/sign in via the agent's own app). -- [ ] **C11 [agent] Per-provider enable/disable.** The `IAgentUsageProvider.IsAvailable` seam - exists but is hardcoded `true`. Add settings toggles so a single-agent user isn't stuck with two - permanent "Sign in" rows. -- [ ] **C12 [agent] Fix misleading empty dock band.** `UsageDockPage.cs` falls through to - "No usage data" / "Can't reach Claude" for an Ok-but-empty account — wrong on both lines. -- [ ] **C13 [agent] Hardcoded strings → resx.** `Program.cs` direct-launch MessageBox + caption; - `Pages/LegalPage.cs` inline legal Markdown (~45 lines). Both violate the project's own rule. +- [x] **C10 [agent] Actionable NotConfigured rows.** (resolved 2026-08-14 — decision: rows stay + deliberately non-actionable; no deep-links or third-party sign-in guidance, the app never handles + or advises on the agents' own apps. Wording made neutral/factual instead: "Not signed in to {0}" / + "No sign-in found on this PC", TokenExpired subtitle likewise de-guided.) +- [x] **C11 [agent] Per-provider enable/disable.** (decided 2026-08-14: **won't do.** Hub showing + all providers — including "Not signed in" rows for unused ones — is acceptable; and the dock + already gives full per-provider control via the host's own pin/unpin per band (one band per + provider), which was the deliberate design. `IsAvailable` stays a dormant seam.) +- [x] **C12 [agent] Fix misleading empty dock band.** (fixed 2026-08-14) Zero-window fall-through + split per status: Ok-but-empty → "No usage data" / "The account reported no active limits"; + RateLimited → "Rate-limited" / rate-limit subtitle; Error keeps "Can't reach {0}". Dock wording + also made neutral per the C10 decision ("Not signed in"; expired subtitle no longer tells the + user to go use the agent — behavior "can change", so no promises). +- [x] **C13 [agent] Hardcoded strings → resx.** (done 2026-08-14) `DirectLaunch_Message` (format + string over `Extension_DisplayName`/`Command_AgentsPanel`; caption reuses `Extension_DisplayName`) + + `Legal_Markdown` moved to resx, Designer.cs in lock-step. No wording changes. ## D. Versioning & build hygiene -- [ ] **D14 [agent] Version/UA consolidation.** Six version sites today: csproj - `AppxPackageVersion`, `Package.appxmanifest`, `app.manifest`, and three hand-duplicated - `UserAgent` constants (Claude/Codex/Copilot providers — each falsely claims "one place to - bump"). Consolidate the UA into one shared constant, then keep `notes/releasing.md`'s bump table - as the single documented list. -- [ ] **D15 [agent] csproj/sln cleanup.** Remove the dead x86 sln configurations (pipeline trap: - ARM64 is picked alphabetically without `-p:Platform`). Optional: preview pins - (`WindowsSdkPackageVersion 10.0.26100.68-preview`, NetAnalyzers preview — MarketExtension - shipped with the same SDK pin); add `//`; fix the wrong - `PrepareAssets` comment ("StoreLogo.png already exists" — it doesn't; MRT resolves it). -- [ ] **D16 [agent] Untrack `.idea/`.** Ignored in `.gitignore` but committed earlier, so it's - still tracked. -- [ ] **D17 [user] Merge `scaffold` → `main`.** All work is on `scaffold`; `origin/main` is one - empty initial commit — the public repo shows nothing. +- [x] **D14 [agent] Version/UA consolidation.** (done 2026-08-14) UA now assembly-derived in + `Helpers/AppInfo.cs` (csproj `` mirrors ``); the three per-provider + constants deleted. Bump sites down to three manifest files — `notes/releasing.md` table updated + and verified (built dll carries 0.1.0.0 → UA "agents-panel/0.1.0", byte-identical to the old + literal). +- [x] **D15 [agent] csproj/sln cleanup.** (done 2026-08-14) `//` + added (verified in the built dll); `PrepareAssets` comment fixed. x86 sln configs KEPT by user + decision (removal reverted). Preview pins (`WindowsSdkPackageVersion 10.0.26100.68-preview`, + NetAnalyzers) deliberately kept — MarketExtension shipped to the Store on the same pin; revisit + post-approval. The ARM64-first alphabetical trap remains — `-p:Platform=x64` stays mandatory. +- [x] **D16 [agent] Untrack `.idea/`.** (verified 2026-08-14: stale item — `git ls-files` shows + nothing under `.idea/` is tracked; nothing to do.) +- [x] **D17 [user] Merge `scaffold` → `main`.** (done — "Initial scaffold (#1)" merged to main; + `scaffold`'s remote is gone. Current work continues on `working_through_release_checklist`, + which will PR to main the same way.) ## E. Release infrastructure (copy from MarketExtension, rename) -- [ ] **E18 [user] Signing cert + secrets.** Copy `create-signing-cert.ps1` (CN already correct), - run as admin, set GitHub secrets `SIGNING_CERT_PFX` (base64) + `SIGNING_CERT_PASSWORD`. - `.gitignore` already covers `*.pfx`. Later, for WinGet: `WINGET_TOKEN` (classic PAT, - public_repo). -- [ ] **E19 [agent] Workflows.** Copy into `.github/workflows/` and rename env vars +- [x] **E18 [user] Signing cert + secrets.** (done 2026-08-14: script at + `AgentsPanelExtension/create-signing-cert.ps1`, cert "Agents Panel Extension Signing" generated + with the manifest CN — expires **2027-08-14**, re-run then — both secrets set on the repo, pfx + git-ignored. User to back up signing.pfx + password.) Still later, for WinGet: `WINGET_TOKEN` + (classic PAT, public_repo). +- [x] **E19 [agent] Workflows.** (build-check/release-msix/deploy-pages written 2026-08-14; release-extension.yml + Inno Setup deferred as noted) Copy into `.github/workflows/` and rename env vars (`DISPLAY_NAME`/`EXTENSION_NAME`/`FOLDER_NAME`): `build-check.yml` (PR gate), `release-msix.yml` (x64+ARM64 → makeappx bundle → signtool → GitHub Release), `deploy-pages.yml`. Optional later: `release-extension.yml` + `build-exe.ps1` + @@ -124,19 +132,23 @@ manifest description written, minimal capabilities, GitHub remote configured. ## G. Recommended, not blocking -- [ ] **G24 [agent] Tests.** Test project over the pure high-risk logic: - `CopilotUsageProvider.MapWindows` (3 payload generations), `UsageRepository.Merge` - (keep-last-good), `UsageSettingsManager.RefreshMinutes` clamp, reset-time parsing — with - captured sample JSON from the three undocumented endpoints as a regression net for when they - reshape. -- [ ] **G25 [agent] Copilot PAT plaintext.** Note it in the setting description + privacy page - (CmdPal TextSetting has no masking). -- [ ] **G26 [agent] Settings wording.** `showModelWindows`/`showExtraUsage` descriptions are - Claude-worded but filter every provider — reword. -- [ ] **G27 [agent] HttpClient timeout.** Verify `HttpRetry`'s 8s bail actually caps wall time; - consider an explicit `Timeout` (default is 100s). -- [ ] **G28 [agent] Delete dead `Assets/LockScreenLogo.scale-200.png`** (unreferenced VS template - leftover). +- [x] **G24 [agent] Tests.** (decided 2026-08-14: **won't do** — user call, no test project.) +- [x] **G25 [agent] Copilot PAT plaintext.** (decided 2026-08-14: **won't do** the setting-description + note — user call. The privacy page (B5) already discloses the plaintext storage, which covers the + formal side.) +- [x] **G26 [agent] Settings wording.** (done 2026-08-14) Descriptions made provider-neutral: + "Show model-specific limits (e.g. Opus, Sonnet) as their own rows" / "Show pay-as-you-go extra + usage when the account reports it" (the old "credits balance" was wrong for Copilot's "n used" + row). +- [x] **G27 [agent] HttpClient timeout.** (decided 2026-08-14: **won't do.** Verified the 8s + `MaxDelay` only caps retry waits, NOT request wall time — the 100s HttpClient default applies per + attempt. Accepted: MarketExtension ships the same default with no issues, the UI never blocks, + and keep-last-good + stale text absorb a slow fetch. Trivial retrofit if ever reported.) +- [x] **G28 [agent] Delete dead `Assets/LockScreenLogo.scale-200.png`** (resolved 2026-08-14: + **kept**, user decision — matching MarketExtension, which shipped the byte-identical file through + Store certification. For the record: it is not actually a valid PNG — its leading `0x89` was + text-mode-corrupted to `EF BF BD` somewhere in MarketExtension's early history — but nothing + references or parses it, so it rides along inert.) ---