mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
Implements the versioning rule (SmartPlant's 5-segment scheme, now the global
standard; currently only sp and zscripts are on it at v1.x):
v{major}.{rc}.{beta}.{alpha}.{build}
zversion: get / bump / bump-stage / set. A stage bump zeroes every lower
segment including build. 'bump' is one per PR and one per defect fix, not per
file. Any write rewrites three things together, because they are only useful
when they agree: build-version.json (source of truth), a '# Version:' line in
all 42 script headers (a lone copied script still says which release it came
from), and CHECKSUMS.txt (stamping changes every file).
zrelease: packages the current version as releases/zscripts-<version>.zip with
a sibling .sha256, for people who want the toolkit without cloning. One hash
verifies the download; the bundled CHECKSUMS.txt verifies the extracted
contents. Refuses to overwrite an existing version's zip (released = immutable;
bump instead), and refuses to package when zchecksums fails. tests/ excluded
from the zip; releases/ never packages itself.
First release included: releases/zscripts-v1.0.0.0.0.zip (42 scripts + 7
support files) and its .sha256.
.gitattributes: releases/*.sha256 pinned LF (sha256sum treats a trailing CR as
part of the filename), releases/*.zip marked binary.
Verified end-to-end as a downloader would experience it, in WSL: sha256sum -c
on the zip passes, unzip, sha256sum -c CHECKSUMS.txt inside gives 42 OK / 0
FAILED, and the extracted zdeploy.ps1 header and build-version.json both read
v1.0.0.0.0. Double-release guard and -Verify mode exercised. Full Pester suite
219/219 (the checksum tests absorb the new files automatically).
174 lines
8.1 KiB
PowerShell
174 lines
8.1 KiB
PowerShell
# Evomedia.net Token Savers — https://github.com/kellymichels/zscripts-token-savers
|
|
# Created by Kelly Michels · dev@evomedia.net
|
|
# Licensed under the MIT License. See LICENSE.
|
|
# Version: v1.0.0.0.0
|
|
|
|
# token-count.ps1 — measure script output volume to estimate AI agent token costs.
|
|
#
|
|
# Runs each z-script and captures lines + characters of output. Use the results
|
|
# to validate or update the figures in TOKEN_SAVINGS.md.
|
|
#
|
|
# Usage:
|
|
# .\token-count.ps1 <project> # run all scripts against a project key
|
|
# .\token-count.ps1 <project> -Skip zbackup_ec2,zrepair # skip slow/destructive ones
|
|
# .\token-count.ps1 <project> -Only zdeploy,zec2 # run specific scripts only
|
|
#
|
|
# Output: a summary table with lines, chars, and estimated tokens per script.
|
|
# Estimated tokens = chars / 3.5 (Claude's approximate tokenization rate).
|
|
|
|
param(
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[string]$Project,
|
|
[string[]]$Skip = @(),
|
|
[string[]]$Only = @()
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
. (Join-Path $PSScriptRoot "ZHelpers.ps1")
|
|
|
|
$CHARS_PER_TOKEN = 3.5
|
|
|
|
$results = [System.Collections.Generic.List[hashtable]]::new()
|
|
|
|
function Invoke-Measured {
|
|
param(
|
|
[string]$Label,
|
|
[string]$ScriptName,
|
|
[scriptblock]$Block,
|
|
[string]$Note = ""
|
|
)
|
|
|
|
if ($Only.Count -gt 0 -and $Only -notcontains $ScriptName) { return }
|
|
if ($Skip -contains $ScriptName) {
|
|
$results.Add(@{ label = $Label; skipped = $true; note = "skipped" })
|
|
return
|
|
}
|
|
|
|
Write-Host "Running $Label..." -ForegroundColor DarkGray -NoNewline
|
|
$tp = Join-Path ([System.IO.Path]::GetTempPath()) ("_zm_" + [System.Guid]::NewGuid().ToString("N") + ".txt")
|
|
$global:_ZMeasuring = $true
|
|
try {
|
|
# Use Start-Transcript so Write-Host (stream 6) is captured.
|
|
# _ZMeasuring tells Start-ZTracking in all called scripts to no-op,
|
|
# so no inner script can replace or stop our transcript.
|
|
Start-Transcript -Path $tp -NoClobber | Out-Null
|
|
try { & $Block } catch {}
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
|
|
$raw = Get-Content -LiteralPath $tp -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
|
|
if (-not $raw) { $raw = "" }
|
|
$allLines = @($raw -split "`n")
|
|
$si = 0
|
|
for ($i = 0; $i -lt $allLines.Count; $i++) {
|
|
if ($allLines[$i] -match '^Transcript started') { $si = $i + 1; break }
|
|
}
|
|
$ei = $allLines.Count
|
|
for ($i = $allLines.Count - 1; $i -ge 0; $i--) {
|
|
if ($allLines[$i] -match '^\*{4}') { $ei = $i; break }
|
|
}
|
|
$body = if ($ei -gt $si) { @($allLines[$si..($ei - 1)]) } else { @() }
|
|
$text = $body -join "`n"
|
|
$lc = ($body | Where-Object { $_.Trim() -ne "" }).Count
|
|
$cc = $text.Length
|
|
$tokens = [math]::Round($cc / $CHARS_PER_TOKEN)
|
|
$results.Add(@{
|
|
label = $Label
|
|
script = $ScriptName
|
|
lines = $lc
|
|
chars = $cc
|
|
tokens = $tokens
|
|
note = $Note
|
|
})
|
|
Write-Host " $lc lines / $cc chars / ~$tokens tokens" -ForegroundColor Green
|
|
} catch {
|
|
$results.Add(@{ label = $Label; script = $ScriptName; error = $_.Exception.Message })
|
|
Write-Host " ERROR: $($_.Exception.Message)" -ForegroundColor Red
|
|
} finally {
|
|
$global:_ZMeasuring = $false
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
Remove-Item -LiteralPath $tp -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "=== Token Count - project: $Project ===" -ForegroundColor Cyan
|
|
Write-Host "Chars per token: $CHARS_PER_TOKEN (Claude approximate)" -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
|
|
# ── Local dev ──────────────────────────────────────────────────────────────────
|
|
Invoke-Measured "zstart " "zstart" { & (Join-Path $PSScriptRoot "zstart.ps1") $Project -Detached } `
|
|
-Note "detached - output may be minimal"
|
|
|
|
Invoke-Measured "zkill " "zkill" { & (Join-Path $PSScriptRoot "ZKillOnly.ps1") $Project }
|
|
|
|
Invoke-Measured "zrestart " "zrestart" { & (Join-Path $PSScriptRoot "ZKiller.ps1") $Project -Detached } `
|
|
-Note "detached"
|
|
|
|
# Takes no project key; brings up zscripts\docker\docker-compose.yml. Needs Docker
|
|
# Desktop running AND that compose file present, or it only emits an error.
|
|
Invoke-Measured "zstart_docker" "zstart_docker" { & (Join-Path $PSScriptRoot "zstart_docker.ps1") } `
|
|
-Note "detached docker stack - needs Docker Desktop + docker/docker-compose.yml"
|
|
|
|
# ── Deploy ─────────────────────────────────────────────────────────────────────
|
|
Invoke-Measured "zdeploy " "zdeploy" { & (Join-Path $PSScriptRoot "zdeploy.ps1") $Project } `
|
|
-Note "includes docker build - run twice; first may be inflated by package installs"
|
|
|
|
# ── Diagnostics ────────────────────────────────────────────────────────────────
|
|
Invoke-Measured "zec2 " "zec2" { & (Join-Path $PSScriptRoot "zec2.ps1") $Project }
|
|
|
|
Invoke-Measured "zec2online" "zec2online" { & (Join-Path $PSScriptRoot "zec2online.ps1") $Project }
|
|
|
|
Invoke-Measured "zrepair " "zrepair" { & (Join-Path $PSScriptRoot "zrepair.ps1") $Project } `
|
|
-Note "may restart containers on EC2"
|
|
|
|
# ── Backups ────────────────────────────────────────────────────────────────────
|
|
Invoke-Measured "zbackup " "zbackup" { & (Join-Path $PSScriptRoot "zbackup.ps1") $Project }
|
|
|
|
Invoke-Measured "zbackup_ec2" "zbackup_ec2" { & (Join-Path $PSScriptRoot "zbackup_ec2.ps1") $Project } `
|
|
-Note "pulls from EC2 - skippable if slow"
|
|
|
|
Invoke-Measured "zsync " "zsync" { & (Join-Path $PSScriptRoot "zsync.ps1") }
|
|
|
|
# ── Summary table ──────────────────────────────────────────────────────────────
|
|
Write-Host ""
|
|
Write-Host "=== Summary ===" -ForegroundColor Cyan
|
|
Write-Host ("{0,-14} {1,8} {2,10} {3,10} {4}" -f "Script", "Lines", "Chars", "~Tokens", "Notes") -ForegroundColor Yellow
|
|
Write-Host ("-" * 70) -ForegroundColor DarkGray
|
|
|
|
$totalTokens = 0
|
|
foreach ($r in $results) {
|
|
if ($r.skipped) {
|
|
Write-Host ("{0,-14} {1,8}" -f $r.label, "(skipped)") -ForegroundColor DarkGray
|
|
} elseif ($r.error) {
|
|
Write-Host ("{0,-14} {1}" -f $r.label, "ERROR: $($r.error)") -ForegroundColor Red
|
|
} else {
|
|
Write-Host ("{0,-14} {1,8} {2,10} {3,10} {4}" -f `
|
|
$r.label, $r.lines, $r.chars, $r.tokens, $r.note) -ForegroundColor White
|
|
$totalTokens += $r.tokens
|
|
}
|
|
}
|
|
|
|
Write-Host ("-" * 70) -ForegroundColor DarkGray
|
|
Write-Host ("{0,-14} {1,8} {2,10} {3,10}" -f "TOTAL", "", "", $totalTokens) -ForegroundColor Cyan
|
|
|
|
# ── Dollar cost at current pricing ─────────────────────────────────────────────
|
|
Write-Host ""
|
|
Write-Host "=== Estimated cost if Claude orchestrated all of the above ===" -ForegroundColor Cyan
|
|
$models = @(
|
|
@{ name = "Haiku 4.5"; blended = 2.25 },
|
|
@{ name = "Sonnet 5 "; blended = 9.00 },
|
|
@{ name = "Opus 4.8 "; blended = 15.00 },
|
|
@{ name = "Fable 5 "; blended = 30.00 }
|
|
)
|
|
foreach ($m in $models) {
|
|
$cost = [math]::Round($totalTokens / 1000000 * $m.blended, 6)
|
|
Write-Host (" {0} ~{1,8} tokens x `${2}/1M blended = `${3}" -f `
|
|
$m.name, $totalTokens, $m.blended, $cost) -ForegroundColor White
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Note: zdeploy output varies significantly between runs." -ForegroundColor DarkGray
|
|
Write-Host " First run after package updates can be 2-3x larger than a cached run." -ForegroundColor DarkGray
|
|
Write-Host " Run zdeploy twice and use the second (cached) figure for TOKEN_SAVINGS.md." -ForegroundColor DarkGray
|
|
Write-Host ""
|