Files
Claude-Code-Monitor/desktop/scripts/build-win-icon.ps1
T
nntrivi2001 57dc91585d feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
2026-07-30 14:39:03 +07:00

122 lines
5.3 KiB
PowerShell

<#
.SYNOPSIS
Generate assets/icon.ico from assets/icon.png — the Windows counterpart to
scripts/build-icons.sh (which produces icon.icns + the macOS tray PNGs).
.DESCRIPTION
Uses only the .NET Framework's System.Drawing (always present on Windows) —
no ImageMagick, no npm dependency. icon.png is the 1024x1024 raster already
rendered from assets/icon.svg by the macOS icon pipeline; this script
downscales it to the standard Windows icon sizes and packs them into a
classic, maximally-compatible BMP-based .ico (32bpp BGRA + AND mask). That
format is what electron-builder embeds in the .exe and what NSIS uses for
the installer icon, and it renders correctly on Windows 7 through 11.
Idempotent. Run from anywhere:
powershell -ExecutionPolicy Bypass -File desktop/scripts/build-win-icon.ps1
.NOTES
Author: Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
#>
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$assets = Join-Path (Split-Path -Parent $here) 'assets'
$srcPng = Join-Path $assets 'icon.png'
$outIco = Join-Path $assets 'icon.ico'
if (-not (Test-Path $srcPng)) {
throw "icon.png not found at $srcPng. Generate it first (scripts/build-icons.sh renders it from icon.svg)."
}
# Standard Windows icon ladder. 256 is required by electron-builder; the small
# sizes keep the taskbar / Alt-Tab / tray crisp.
$sizes = 16, 24, 32, 48, 64, 128, 256
$src = [System.Drawing.Image]::FromFile($srcPng)
$entries = New-Object System.Collections.ArrayList
try {
foreach ($s in $sizes) {
$bmp = New-Object System.Drawing.Bitmap($s, $s, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$g.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality
$g.Clear([System.Drawing.Color]::Transparent)
$g.DrawImage($src, 0, 0, $s, $s)
$g.Dispose()
# Pull raw pixels: Format32bppArgb is stored little-endian as B,G,R,A —
# exactly the byte order a 32bpp DIB wants. Rows are top-down here.
$rect = New-Object System.Drawing.Rectangle(0, 0, $s, $s)
$data = $bmp.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::ReadOnly, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
$stride = $data.Stride
$buf = New-Object byte[] ($stride * $s)
[System.Runtime.InteropServices.Marshal]::Copy($data.Scan0, $buf, 0, $buf.Length)
$bmp.UnlockBits($data)
$bmp.Dispose()
# Build the DIB: BITMAPINFOHEADER(40) + XOR bitmap (bottom-up BGRA) +
# 1bpp AND mask (bottom-up, all zeros — alpha channel does the masking).
$ms = New-Object System.IO.MemoryStream
$bw = New-Object System.IO.BinaryWriter($ms)
$bw.Write([int]40) # biSize
$bw.Write([int]$s) # biWidth
$bw.Write([int]($s * 2)) # biHeight = XOR height + AND height
$bw.Write([int16]1) # biPlanes
$bw.Write([int16]32) # biBitCount
$bw.Write([int]0) # biCompression = BI_RGB
$bw.Write([int]0) # biSizeImage
$bw.Write([int]0) # biXPelsPerMeter
$bw.Write([int]0) # biYPelsPerMeter
$bw.Write([int]0) # biClrUsed
$bw.Write([int]0) # biClrImportant
# XOR pixels, bottom-up.
for ($y = $s - 1; $y -ge 0; $y--) {
$bw.Write($buf, $y * $stride, 4 * $s)
}
# AND mask: 1 bit/pixel, each row padded to a 4-byte boundary, all zero.
$maskRow = [int]([math]::Floor((($s + 31) / 32)) * 4)
$zeros = New-Object byte[] ($maskRow)
for ($y = 0; $y -lt $s; $y++) { $bw.Write($zeros, 0, $maskRow) }
$bw.Flush()
[void]$entries.Add([pscustomobject]@{ Size = $s; Data = $ms.ToArray() })
$bw.Dispose(); $ms.Dispose()
}
}
finally {
$src.Dispose()
}
# Assemble the .ico: ICONDIR header, then one ICONDIRENTRY per image, then data.
$out = New-Object System.IO.MemoryStream
$w = New-Object System.IO.BinaryWriter($out)
$w.Write([int16]0) # reserved
$w.Write([int16]1) # type = icon
$w.Write([int16]$entries.Count) # image count
$offset = 6 + 16 * $entries.Count
foreach ($e in $entries) {
$dim = if ($e.Size -ge 256) { 0 } else { $e.Size } # 0 means 256 in the dir
$w.Write([byte]$dim) # width
$w.Write([byte]$dim) # height
$w.Write([byte]0) # palette color count
$w.Write([byte]0) # reserved
$w.Write([int16]1) # color planes
$w.Write([int16]32) # bits per pixel
$w.Write([int]$e.Data.Length) # size of image data
$w.Write([int]$offset) # offset of image data
$offset += $e.Data.Length
}
foreach ($e in $entries) { $w.Write($e.Data, 0, $e.Data.Length) }
$w.Flush()
[System.IO.File]::WriteAllBytes($outIco, $out.ToArray())
$w.Dispose(); $out.Dispose()
Write-Output ("Wrote {0} ({1:N0} bytes, sizes: {2})" -f $outIco, (Get-Item $outIco).Length, ($sizes -join ', '))