mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
zdeploy ended with two blank lines and nothing else did, so its output
was the only one that did not butt up against the next prompt. Applied
everywhere.
Implemented in Stop-ZTracking rather than per script, because every
tracked script ends by calling it - including the usage and guard paths
that do `Stop-ZTracking; exit 1`. One place therefore covers every exit
of sixteen scripts.
* Write-ZTrailer emits the two lines. Stop-ZTracking calls it on both
paths, including the early return when tracking never started - a
script that printed output still deserves the separation.
* -FinalNote prints one last line AFTER the tracking footer and BEFORE
the blanks. zdeploy's "Last deployed at ..." now goes through it, so
it stays the last thing on screen instead of being followed by the
token count.
* The standalone tools (zchecksums, zversion, zrelease) get a local
three-line copy at each exit rather than dot-sourcing ZHelpers -
they are deliberately dependency-free so they run from an extracted
release zip with nothing beside them.
* Redundant trailing blanks were removed where a script already
printed one before exiting, so the count is exactly two, not three.
Also fixes a related gap found while testing: the error exits inside
Get-ZConfig and Get-ZProject bypassed Stop-ZTracking entirely, so a run
that died on a missing zconfig.json printed no trailer AND no token
footer. Those three exits now route through Stop-ZTracking.
zkill/zrestart are one-line wrappers around scripts that already trail,
so they are untouched - adding it would double the blanks.
Verified by running each script and counting the trailing blank lines in
its captured output: 2 on every success path, every usage path, and the
config-error path. Suite 231/231. CHECKSUMS.txt refreshed.
174 lines
8.1 KiB
PowerShell
174 lines
8.1 KiB
PowerShell
# Evomedia.net Token Savers — https://github.com/evomedia-net/evo.zscripts
|
|
# Created by Kelly Michels · dev@evomedia.net
|
|
# Licensed under the MIT License. See LICENSE.
|
|
# Version: v1.0.0.0.14
|
|
|
|
# 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-ZTrailer
|