Import current workspace

This commit is contained in:
2026-02-25 08:27:37 +11:00
commit 9233275e8c
7 changed files with 2756 additions and 0 deletions

204
README.md Normal file
View File

@@ -0,0 +1,204 @@
# Portable Caffeine (PowerShell 5.1 + Embedded C#)
Portable Windows implementation of the Zhorn Software Caffeine utility, built as a single `caffeine.ps1` script with embedded C# (`WinForms` + Win32 API calls).
## What It Does
Prevents sleep/idle by either:
- Sending periodic key pulses (default `F15`)
- Using `SetThreadExecutionState` (`-stes`)
It also includes a tray icon app, status dialog, timers, rule-based activation, and single-instance app control commands.
## Files
- `caffeine.ps1`: main portable app
- `run-caffeine.bat`: launcher that runs with `-ExecutionPolicy Bypass -STA`
- `build-caffeine-exe.ps1`: extracts embedded C# and compiles a standalone `.exe`
- `sign-caffeine-exe.ps1`: creates/reuses a self-signed code-signing cert, optionally trusts it, and signs the `.exe`
## Requirements
- Windows
- Windows PowerShell 5.1
- Desktop session (uses `System.Windows.Forms` + tray icon)
## Quick Start
Run with the batch launcher:
```bat
run-caffeine.bat -showdlg -notify
```
Or directly:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -STA -File .\caffeine.ps1 -showdlg -notify
```
## Execution Policy Note
If PowerShell blocks script execution, use the launcher (`run-caffeine.bat`) or run:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -STA -File .\caffeine.ps1
```
Optional:
```powershell
Unblock-File .\caffeine.ps1
```
## Supported Features / Switches
Core:
- `-on` / `-off`
- `-activefor:N`
- `-inactivefor:N`
- `-exitafter:N`
- `-lock`
- `-notify`
- `-showdlg`
- `-ontaskbar`
- `-replace`
Single-instance app commands:
- `-appexit`
- `-appon`
- `-appoff`
- `-apptoggle`
- `-apptoggleshowdlg`
Activity methods:
- Default F15 key pulse
- `-useshift`
- `-leftshift`
- `-key:NN` / `-keypress:NN`
- `-keyshift[:NN]`
- `-stes`
- `-allowss`
Conditional activation:
- `-watchwindow:TEXT`
- `-activeperiods:RANGES`
- `-activehours:RANGES` (alias)
- `-onac`
- `-cpu:N`
Tray / icon behavior:
- Custom coffee-cup tray icon (active/inactive variants)
- `-nohicon` keeps the same tray icon in both states
- Double-click tray icon toggles active/inactive
Compatibility:
- `-allowlocal` is recognized (compatibility flag)
## Examples
```bat
run-caffeine.bat -showdlg -notify
run-caffeine.bat -activefor:30
run-caffeine.bat -watchwindow:Notepad -on
run-caffeine.bat -activeperiods:08:00-12:00,13:00-17:00
run-caffeine.bat -stes -allowss
run-caffeine.bat -apptoggle
run-caffeine.bat -replace -off
```
## Compile To EXE (Windows)
You can build a standalone executable from the embedded C# code in `caffeine.ps1`:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\build-caffeine-exe.ps1
```
Output (default):
- `dist\PortableCaffeine.exe`
Useful options:
```powershell
# Keep the extracted .cs file used for compilation
.\build-caffeine-exe.ps1 -KeepExtractedSource
# Build a console-targeted EXE instead of WinExe
.\build-caffeine-exe.ps1 -ConsoleTarget
# Custom output name/path
.\build-caffeine-exe.ps1 -OutputDir .\out -ExeName Caffeine.exe
```
Notes:
- The build script looks for `.NET Framework` `csc.exe` on Windows (Framework64 first, then Framework).
- The compiled EXE uses the same tray UI, switches, and behavior as the PowerShell-hosted version.
## Self-Signed Code Signing (Dev/Test)
You can sign the compiled EXE with a locally generated self-signed code-signing certificate.
Default behavior:
- Reuses an existing cert matching the configured subject (if present)
- Otherwise creates a new self-signed code-signing cert
- Exports the public cert to `dist\PortableCaffeine-dev-signing.cer`
- Installs trust in `CurrentUser\Root` and `CurrentUser\TrustedPublisher`
- Signs `dist\PortableCaffeine.exe` with `Set-AuthenticodeSignature`
Command:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\sign-caffeine-exe.ps1
```
Machine-wide trust (all users, requires admin):
```powershell
# Run PowerShell as Administrator
.\sign-caffeine-exe.ps1 -TrustScope LocalMachine
```
Notes on admin requirements:
- `CurrentUser` trust/store operations do not require admin.
- `LocalMachine` trust (`Root` / `TrustedPublisher`) requires an elevated PowerShell session.
- Creating the private key in `LocalMachine\My` also requires admin (`-PrivateKeyStoreScope LocalMachine`).
Useful options:
```powershell
# Force creation of a new self-signed certificate instead of reusing an existing one
.\sign-caffeine-exe.ps1 -ForceNewCertificate
# Sign without installing trust stores (signature will be present but likely untrusted)
.\sign-caffeine-exe.ps1 -TrustScope None
# Export a PFX (password required)
.\sign-caffeine-exe.ps1 -PfxExportPath .\dist\PortableCaffeine-dev-signing.pfx -PfxPassword (Read-Host -AsSecureString)
# Use a timestamp server (optional)
.\sign-caffeine-exe.ps1 -TimestampServer http://timestamp.digicert.com
```
Security note:
- Trusting a self-signed certificate in `Root` makes your machine trust anything signed by that certificate.
- Use a dedicated dev/test certificate, protect the private key, and remove trust when no longer needed.
- This is suitable for local/internal testing, not a substitute for a public CA-issued code-signing certificate.
## Notes
- This project is intended to be portable (no install required).
- The implementation targets Windows PowerShell 5.1 and Windows desktop APIs.
- Exact visual/behavioral parity with the original Zhorn Caffeine may differ slightly in some edge cases.

44
agent.md Normal file
View File

@@ -0,0 +1,44 @@
## Workflow Orchestration
### 1. Plan Node Default
- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
- If something goes sideways, STOP and re-plan immediately - don't keep pushing
- Use plan mode for verification steps, not just building
- Write detailed specs upfront to reduce ambiguity
### 2. Subagent Strategy
- Use subagents liberally to keep main context window clean
- Offload research, exploration, and parallel analysis to subagents
- For complex problems, throw more compute at it via subagents
- One tack per subagent for focused execution
### 3. Self-Improvement Loop
- After ANY correction from the user: update tasks/lessons.md
with the pattern
- Write rules for yourself that prevent the same mistake
- Ruthlessly iterate on these lessons until mistake rate drops
- Review lessons at session start for relevant project
### 4. Verification Before Done
- Never mark a task complete without proving it works
- Diff behavior between main and your changes when relevant
- Ask yourself: "Would a staff engineer approve this?"
- Run tests, check logs, demonstrate correctness
### 5. Demand Elegance (Balanced)
- For non-trivial changes: pause and ask "is there a more elegant way?"
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
- Skip this for simple, obvious fixes - don't over-engineer
- Challenge your own work before presenting it
### 6. Autonomous Bug Fizing
- When given a bug report: just fix it. Don't ask for hand-holding
- Point at logs, errors, failing tests - then resolve them
- Zero context switching required from the user
- Go fix failing CI tests without being told how
## Task Management
1. **Plan First**: Write plan to "tasks/todo.md with checkable items
2. **Verify Plan**: Check in before starting implementation
3. **Track Progress**: Mark items complete as you go
4. **Explain Changes**: High-level summary at each step
5. **Document Results**: Add review section to tasks/todo.md"
6. **Capture Lessons**: Update tasks/lessons. md after corrections
## Core Principles
- **Simplicity First**: Make every change as simple as possible. Impact minimal code.
- **No Laziness**: Find root causes. No temporary fixes, Senior developer standards.
- **Minimat Impact**: Changes should only touch what's necessary. Avoid introducing bugs.

101
build-caffeine-exe.ps1 Normal file
View File

@@ -0,0 +1,101 @@
[CmdletBinding()]
param(
[string]$SourceScript = (Join-Path $PSScriptRoot 'caffeine.ps1'),
[string]$OutputDir = (Join-Path $PSScriptRoot 'dist'),
[string]$BuildDir = (Join-Path $PSScriptRoot 'build'),
[string]$ExeName = 'PortableCaffeine.exe',
[switch]$ConsoleTarget,
[switch]$KeepExtractedSource
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-CSharpFromCaffeineScript {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Source script not found: $Path"
}
$raw = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
$pattern = '(?s)\$csharp\s*=\s*@''\r?\n(?<code>.*?)\r?\n''@'
$match = [regex]::Match($raw, $pattern)
if (-not $match.Success) {
throw "Could not locate embedded C# here-string (`$csharp = @' ... '@) in $Path"
}
return $match.Groups['code'].Value
}
function Get-CscPath {
$candidates = @(
(Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319\csc.exe'),
(Join-Path $env:WINDIR 'Microsoft.NET\Framework\v4.0.30319\csc.exe')
)
foreach ($candidate in $candidates) {
if ($candidate -and (Test-Path -LiteralPath $candidate)) {
return $candidate
}
}
$cmd = Get-Command csc.exe -ErrorAction SilentlyContinue
if ($cmd) {
return $cmd.Source
}
throw "csc.exe not found. Install .NET Framework build tools (or run on Windows with .NET Framework 4.x)."
}
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
throw "This build script is intended for Windows because it compiles WinForms code against .NET Framework (csc.exe)."
}
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null
$csharpSource = Get-CSharpFromCaffeineScript -Path $SourceScript
$generatedCsPath = Join-Path $BuildDir 'PortableCaffeine.generated.cs'
$outExePath = Join-Path $OutputDir $ExeName
$targetKind = if ($ConsoleTarget) { 'exe' } else { 'winexe' }
$cscPath = Get-CscPath
[System.IO.File]::WriteAllText($generatedCsPath, $csharpSource, (New-Object System.Text.UTF8Encoding($false)))
$arguments = @(
'/nologo'
"/target:$targetKind"
'/optimize+'
'/platform:anycpu'
"/out:$outExePath"
'/r:System.dll'
'/r:System.Windows.Forms.dll'
'/r:System.Drawing.dll'
$generatedCsPath
)
Write-Host "Compiling with: $cscPath"
Write-Host "Target: $targetKind"
Write-Host "Output: $outExePath"
& $cscPath @arguments
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
throw "csc.exe failed with exit code $exitCode"
}
if (-not $KeepExtractedSource) {
Remove-Item -LiteralPath $generatedCsPath -Force -ErrorAction SilentlyContinue
}
Write-Host ""
Write-Host "Build succeeded."
Write-Host "EXE: $outExePath"
if ($KeepExtractedSource) {
Write-Host "Extracted source kept at: $generatedCsPath"
}

1982
caffeine.ps1 Normal file

File diff suppressed because it is too large Load Diff

13
run-caffeine.bat Normal file
View File

@@ -0,0 +1,13 @@
@echo off
setlocal
set "SCRIPT_DIR=%~dp0"
set "PS_SCRIPT=%SCRIPT_DIR%caffeine.ps1"
if not exist "%PS_SCRIPT%" (
echo Could not find "%PS_SCRIPT%".
exit /b 1
)
powershell.exe -NoProfile -ExecutionPolicy Bypass -STA -File "%PS_SCRIPT%" %*
exit /b %ERRORLEVEL%

378
sign-caffeine-exe.ps1 Normal file
View File

@@ -0,0 +1,378 @@
[CmdletBinding()]
param(
[string]$ExePath = (Join-Path $PSScriptRoot 'dist\PortableCaffeine.exe'),
[string]$Subject = 'CN=Portable Caffeine Dev Code Signing',
[string]$FriendlyName = 'Portable Caffeine Dev Code Signing (Self-Signed)',
[ValidateSet('None', 'CurrentUser', 'LocalMachine')]
[string]$TrustScope = 'CurrentUser',
[ValidateSet('CurrentUser', 'LocalMachine')]
[string]$PrivateKeyStoreScope = 'CurrentUser',
[int]$YearsValid = 5,
[switch]$ForceNewCertificate,
[string]$TimestampServer,
[string]$CerExportPath = (Join-Path $PSScriptRoot 'dist\PortableCaffeine-dev-signing.cer'),
[string]$PfxExportPath,
[securestring]$PfxPassword
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
throw "This script is intended for Windows only."
}
if (-not (Get-Command New-SelfSignedCertificate -ErrorAction SilentlyContinue)) {
throw "New-SelfSignedCertificate is not available in this PowerShell session."
}
if (-not (Get-Command Set-AuthenticodeSignature -ErrorAction SilentlyContinue)) {
throw "Set-AuthenticodeSignature is not available in this PowerShell session."
}
if (-not ('PortableCaffeineBuild.SigningHelpers' -as [type])) {
$helperCode = @"
using System;
using System.Security.Principal;
using System.Security.Cryptography.X509Certificates;
namespace PortableCaffeineBuild
{
public static class SigningHelpers
{
public static bool IsAdministrator()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (identity == null)
{
return false;
}
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
public static bool StoreContainsThumbprint(string storeName, string storeLocation, string thumbprint)
{
if (string.IsNullOrEmpty(thumbprint))
{
return false;
}
StoreName sn = (StoreName)Enum.Parse(typeof(StoreName), storeName, true);
StoreLocation sl = (StoreLocation)Enum.Parse(typeof(StoreLocation), storeLocation, true);
X509Store store = new X509Store(sn, sl);
try
{
store.Open(OpenFlags.ReadOnly);
string normalized = thumbprint.Replace(" ", string.Empty).ToUpperInvariant();
foreach (X509Certificate2 cert in store.Certificates)
{
if (cert == null || string.IsNullOrEmpty(cert.Thumbprint))
{
continue;
}
if (cert.Thumbprint.Replace(" ", string.Empty).ToUpperInvariant() == normalized)
{
return true;
}
}
return false;
}
finally
{
store.Close();
}
}
public static void AddPublicCertificateToStore(byte[] rawData, string storeName, string storeLocation)
{
if (rawData == null || rawData.Length == 0)
{
throw new ArgumentException("rawData");
}
X509Certificate2 publicCert = new X509Certificate2(rawData);
try
{
if (StoreContainsThumbprint(storeName, storeLocation, publicCert.Thumbprint))
{
return;
}
StoreName sn = (StoreName)Enum.Parse(typeof(StoreName), storeName, true);
StoreLocation sl = (StoreLocation)Enum.Parse(typeof(StoreLocation), storeLocation, true);
X509Store store = new X509Store(sn, sl);
try
{
store.Open(OpenFlags.ReadWrite);
store.Add(publicCert);
}
finally
{
store.Close();
}
}
finally
{
publicCert.Reset();
}
}
}
}
"@
Add-Type -TypeDefinition $helperCode -ReferencedAssemblies @(
'System.dll'
)
}
function Test-IsAdministrator {
return [PortableCaffeineBuild.SigningHelpers]::IsAdministrator()
}
function Get-CodeSigningCertificate {
param(
[Parameter(Mandatory = $true)]
[string]$SubjectName,
[Parameter(Mandatory = $true)]
[string]$StoreScope
)
$storePath = "Cert:\$StoreScope\My"
if (-not (Test-Path -LiteralPath $storePath)) {
return $null
}
$now = Get-Date
$certs = Get-ChildItem -Path $storePath |
Where-Object {
$_.Subject -eq $SubjectName -and
$_.HasPrivateKey -and
$_.NotAfter -gt $now
} |
Sort-Object -Property NotAfter -Descending
if ($certs) {
return @($certs)[0]
}
return $null
}
function New-CodeSigningCertificate {
param(
[Parameter(Mandatory = $true)]
[string]$SubjectName,
[Parameter(Mandatory = $true)]
[string]$Friendly,
[Parameter(Mandatory = $true)]
[string]$StoreScope,
[Parameter(Mandatory = $true)]
[int]$ValidYears
)
$certStoreLocation = "Cert:\$StoreScope\My"
$notAfter = (Get-Date).AddYears($ValidYears)
Write-Host "Creating new self-signed code signing certificate..."
Write-Host "Subject: $SubjectName"
Write-Host "Store: $certStoreLocation"
$newCert = New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject $SubjectName `
-FriendlyName $Friendly `
-CertStoreLocation $certStoreLocation `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-KeySpec Signature `
-HashAlgorithm SHA256 `
-KeyExportPolicy Exportable `
-NotAfter $notAfter
if (-not $newCert) {
throw "New-SelfSignedCertificate did not return a certificate."
}
return $newCert
}
function Install-CertificateTrust {
param(
[Parameter(Mandatory = $true)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
[Parameter(Mandatory = $true)]
[ValidateSet('CurrentUser', 'LocalMachine')]
[string]$Scope
)
foreach ($storeName in @('Root', 'TrustedPublisher')) {
$alreadyTrusted = [PortableCaffeineBuild.SigningHelpers]::StoreContainsThumbprint($storeName, $Scope, $Certificate.Thumbprint)
if ($alreadyTrusted) {
Write-Host "Trusted store already contains cert: $Scope\$storeName ($($Certificate.Thumbprint))"
continue
}
Write-Host "Adding cert to trusted store: $Scope\$storeName"
[PortableCaffeineBuild.SigningHelpers]::AddPublicCertificateToStore($Certificate.RawData, $storeName, $Scope)
}
}
function Export-PublicCertificateIfRequested {
param(
[Parameter(Mandatory = $true)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
[string]$Path
)
if ([string]::IsNullOrWhiteSpace($Path)) {
return
}
$dir = Split-Path -Parent $Path
if ($dir) {
New-Item -ItemType Directory -Force -Path $dir | Out-Null
}
$raw = $Certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
[System.IO.File]::WriteAllBytes($Path, $raw)
Write-Host "Exported public certificate: $Path"
}
function Export-PfxIfRequested {
param(
[Parameter(Mandatory = $true)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
[string]$Path,
[securestring]$Password
)
if ([string]::IsNullOrWhiteSpace($Path)) {
return
}
if (-not $Password) {
throw "PfxExportPath was provided but no -PfxPassword was supplied."
}
if (-not (Get-Command Export-PfxCertificate -ErrorAction SilentlyContinue)) {
throw "Export-PfxCertificate is not available in this PowerShell session."
}
$dir = Split-Path -Parent $Path
if ($dir) {
New-Item -ItemType Directory -Force -Path $dir | Out-Null
}
Export-PfxCertificate -Cert $Certificate -FilePath $Path -Password $Password -Force | Out-Null
Write-Host "Exported PFX: $Path"
}
function Sign-Executable {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
[string]$TimestampUrl
)
$params = @{
FilePath = $Path
Certificate = $Certificate
HashAlgorithm = 'SHA256'
}
if (-not [string]::IsNullOrWhiteSpace($TimestampUrl)) {
$params['TimestampServer'] = $TimestampUrl
}
Write-Host "Signing file: $Path"
$result = Set-AuthenticodeSignature @params
if (-not $result) {
throw "Set-AuthenticodeSignature did not return a result."
}
Write-Host "Sign status: $($result.Status)"
if ($result.StatusMessage) {
Write-Host "Status msg: $($result.StatusMessage)"
}
return $result
}
if (-not (Test-Path -LiteralPath $ExePath)) {
throw "EXE not found: $ExePath`nBuild it first with .\build-caffeine-exe.ps1"
}
if ($YearsValid -lt 1 -or $YearsValid -gt 50) {
throw "YearsValid must be between 1 and 50."
}
$needsAdmin = $false
if ($TrustScope -eq 'LocalMachine' -or $PrivateKeyStoreScope -eq 'LocalMachine') {
$needsAdmin = $true
}
if ($needsAdmin -and -not (Test-IsAdministrator)) {
throw "This operation requires an elevated PowerShell session (Run as Administrator) because LocalMachine certificate stores are being modified/used."
}
$cert = $null
if (-not $ForceNewCertificate) {
$cert = Get-CodeSigningCertificate -SubjectName $Subject -StoreScope $PrivateKeyStoreScope
}
if (-not $cert) {
$cert = New-CodeSigningCertificate -SubjectName $Subject -Friendly $FriendlyName -StoreScope $PrivateKeyStoreScope -ValidYears $YearsValid
} else {
Write-Host "Reusing existing certificate:"
Write-Host "Subject: $($cert.Subject)"
Write-Host "Thumbprint: $($cert.Thumbprint)"
Write-Host "Expires: $($cert.NotAfter)"
Write-Host "Store: Cert:\$PrivateKeyStoreScope\My"
}
Export-PublicCertificateIfRequested -Certificate $cert -Path $CerExportPath
Export-PfxIfRequested -Certificate $cert -Path $PfxExportPath -Password $PfxPassword
if ($TrustScope -ne 'None') {
Write-Host ""
Write-Host "Installing trust for self-signed certificate..."
Install-CertificateTrust -Certificate $cert -Scope $TrustScope
} else {
Write-Host "Skipping trust-store installation (TrustScope=None)."
}
Write-Host ""
$signResult = Sign-Executable -Path $ExePath -Certificate $cert -TimestampUrl $TimestampServer
$verify = Get-AuthenticodeSignature -FilePath $ExePath
Write-Host ""
Write-Host "Verification:"
Write-Host "Status: $($verify.Status)"
if ($verify.SignerCertificate) {
Write-Host "Thumbprint: $($verify.SignerCertificate.Thumbprint)"
Write-Host "Subject: $($verify.SignerCertificate.Subject)"
}
if ($verify.TimeStamperCertificate) {
Write-Host "Timestamped: Yes"
} else {
Write-Host "Timestamped: No"
}
if ($verify.Status -ne 'Valid') {
Write-Warning "Signature was applied but verification is not 'Valid'. For self-signed certs, ensure trust was installed in Root + TrustedPublisher for the relevant scope."
}
Write-Host ""
Write-Host "Done."
Write-Host "EXE: $ExePath"
if ($CerExportPath) {
Write-Host "CER: $CerExportPath"
}

34
tasks/todo.md Normal file
View File

@@ -0,0 +1,34 @@
# Portable Caffeine Clone (PowerShell 5.1 + Embedded C#)
## Plan
- [x] Confirm feature parity target from https://www.zhornsoftware.co.uk/caffeine/ (current switches + tray behavior)
- [x] Define implementation architecture (single script, embedded C# helpers, tray UI, single-instance IPC)
- [x] Implement command-line parsing and normalized parameter model
- [x] Implement activity engine (F15/Shift/custom key, key up vs keypress, STES, allow screensaver)
- [x] Implement conditional activation rules (`-watchwindow`, `-activeperiods`/`-activehours`, `-onac`, `-cpu`)
- [x] Implement timers (`-exitafter`, `-activefor`, `-inactivefor`) and state transitions
- [x] Implement tray app UX (double-click toggle, active/inactive menu, timed menu, revert-to-parameters, about, exit)
- [x] Implement single-instance behavior and remote app commands (`-appexit`, `-appon`, `-appoff`, `-apptoggle`, `-apptoggleshowdlg`, `-replace`)
- [x] Implement notifications (`-notify`) and optional status dialog (`-showdlg`, `-ontaskbar`)
- [ ] Verify script loads/parses and document limitations from non-Windows validation environment
## Progress Notes
- Initial scope confirmed from Zhorn feature list (includes v1.98-era switches like `-activeperiods` and `-notify`).
- Implemented `caffeine.ps1` as a portable single-script app with embedded C# WinForms runtime.
- Added single-instance IPC via hidden window + `WM_COPYDATA` for `-app*` commands and `-replace`.
- Added conditional activation (`-watchwindow`, `-activeperiods`, `-activehours`, `-onac`, `-cpu`) and timers (`-activefor`, `-inactivefor`, `-exitafter`).
- Added activity methods (default F15 key pulse, Shift variants/custom keys, `-stes`, `-allowss`) and tray/status UX.
- Updated tray icons to custom coffee-cup active/inactive variants (with `-nohicon` preserving the same icon in both states).
- Added `build-caffeine-exe.ps1` to extract embedded C# from `caffeine.ps1` and compile `dist\PortableCaffeine.exe` with `csc.exe`.
- Added a `Main()` entry point to the embedded `PortableCaffeine.Program` class so the same source compiles directly as an EXE.
- Added `sign-caffeine-exe.ps1` (PowerShell + embedded C# helper) to create/reuse a self-signed code-signing cert, optionally trust it (`CurrentUser`/`LocalMachine`), and sign the compiled EXE.
- Static review patch: fixed ambiguous `Timer` type and prevented `-app*` switches from starting a new instance when no instance is running.
## Review
- Runtime verification is still pending because this environment is macOS and does not have `pwsh` installed, so I could not run a PowerShell parser check or Windows UI/API smoke test here.
- EXE build verification is also pending in this environment (no Windows `.NET Framework` `csc.exe` available here).
- Self-signed certificate creation/trust-store installation/signing verification is also pending in this environment (requires Windows cert stores and `Set-AuthenticodeSignature`).
- The implementation is intended for Windows PowerShell 5.1 (STA) and relies on `System.Windows.Forms`, `user32.dll`, and `kernel32.dll`.