* chore: write the site name as evomedia.net, lowercase
The name is a domain and is written as one. Script headers, the README,
CHANGELOG and elevator pitch, their .txt twins, and the site page --
matching the same sweep in the private evo.scripts so the mirror does not
drift.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: refresh CHECKSUMS.txt for the lowercase sweep
Every script's header changed, so every hash did. The repo's own
Checksums test caught it -- which is what it is for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
"Downloading without cloning" was bash only, in the README of a PowerShell
toolkit. It is also the first thing a stranger does, so the one section aimed
squarely at newcomers was the one they could not run: `sha256sum` and `unzip`
are not Windows commands at all, and `&&` is a parse error in PowerShell 5.1
rather than a wrong result.
PowerShell goes first here, because these commands are PowerShell - Get-FileHash
against the .sha256, Expand-Archive, then zchecksums.cmd for the contents. The
bash form stays below it, matching how "Verifying what you downloaded" already
leads with zchecksums and offers sha256sum second.
Tested against releases/zscripts-v1.0.0.0.9.zip: the comparison returns True.
It also gets a note, because the output invites a wrong conclusion -
Get-FileHash prints upper case and the .sha256 file holds lower, so the two
strings look different side by side. PowerShell's -eq is case-insensitive on
strings, so the check is right and the eyes are wrong.
Twins regenerated; the Pester twin-sync test passes.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat: add zmerge and zpull
Two commands that were private-only until now. They turned out to be useful
beyond the fleet they were written for, so they are manifested for publication
and removed from the sanitization denylist's private-only list.
zmerge merge every pull request across the org that is genuinely ready -
MERGEABLE/CLEAN and not a draft - re-checking each one immediately
before and after every merge, because merging into a default branch
can conflict a sibling PR in the same repository. Dry run by
default; -Execute or -e merges.
zpull zmerge, then git pull --ff-only in every checkout the merges
affected. Skips a checkout that is dirty or is not on its default
branch rather than guessing at it.
WHY THEY COULD BE PUBLISHED NOW. zmerge carried a hardcoded list of sixteen
repository names, which was both the reason it could not be published and a
bug: the org has thirty active repositories, so it scanned about half and
reported "Nothing open to merge" while a ready pull request sat in one it had
never heard of. It asks GitHub now, and the names went with the list.
Get-FleetRepos throws rather than returning an empty list when gh fails,
because a tool that quietly scans nothing prints the same reassuring line as
one that scanned everything and found nothing.
-e is an alias for -Execute on both, the way -s already works for -Scan.
Also: __pycache__/ is gitignored. scripts/plaintext_twins.py creates it on
every run and it was showing up as untracked work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: CRLF the new scripts, as .gitattributes pins them
zmerge.ps1, zpull.ps1 and zpull.cmd went in with LF endings. .gitattributes
pins *.ps1 and *.cmd to eol=crlf precisely so CHECKSUMS.txt can hold one hash
per file rather than one per platform - so git handed CI a CRLF checkout while
the manifest carried hashes taken from my LF copies, and the three new files
were the only ones that failed.
Local verification passed and CI did not, which is the tell: the manifest was
generated against bytes that only existed on this machine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The mirror carries the tagOnDeploy feature, its tests, and the zstart
gitPull fix from the private tree. Twelve published references to
current product names and internal issue numbers are reworded
generically, the denylist learns the current spellings (a dot or hyphen
broke the word, an underscore hid the boundary), and planted cases prove
the suite now sees them. CHECKSUMS.txt regenerated.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The mirror's deploy pair had drifted ~280 lines behind: it lacked the
transactional .env preserve/restore (an interrupted deploy could destroy
server-side env files), the stderr-flattening step wrapper (a successful
deploy reported failure and skipped its own verification), and the
verification rework (channel re-picked every retry, edge only with a Host
to route by, verify.timeoutSeconds, honest split of "stale build" vs "no
channel answered").
The sync is byte-faithful to the private tree except where the mirror's
own Sanitization suite demands otherwise - and it caught the first copy:
three failures for private project names, a private domain, and a
private-only script name that rode along in comments. Each war story keeps
its lesson and loses its cast, per the convention already in the file
("EvoCivilCode: deploy/" was already published as "(deploy/, infra/,
...)"). That suite going red on an unsanitized copy is exactly what it
exists for.
Also in this change:
- tests/VerifyPlan.Tests.ps1 - the channel-selection rules are pure
functions and Pester pins them (no domain => no edge attempt; the PS 5.1
one-element-unroll trap). First verification tests in the mirror.
- README: the verify block now documents viaProxy/upstream (they shipped
in the docker-network read but were never in the README),
timeoutSeconds, and the channel order with why it re-resolves per retry.
- README.txt: generated plain-text twin, via scripts/readme_txt.py
(vendored from the fleet's reference implementation; the file is
generated, never edited by hand).
- CHANGELOG.md/.txt: entries merged into the existing Unreleased sections.
CHECKSUMS.txt refreshed (42 entries). Pester: 240 passed, 0 failed.
Both synced files parse clean.
Observed, untouched: the Unreleased section carries duplicate "### Fixed"
headings from earlier appends; folding them risks reordering entries whose
prose references their neighbours, so it is left for the next release cut
(zbump #110 rolls Unreleased into the version being cut).
All 16 repos moved to the evomedia-net org and were renamed into the evo.*
namespace, so every github.com/kellymichels/<old-name> reference in source
headers, CI badges, security links and docs pointed at a redirect.
Mechanical URL-only rewrite, applied longest-name-first so smartplantehs-docs
could not be clobbered by the smartplantehs rule. Nothing else changes: no
code, no product names, no behaviour. smartplantehs -> evo.ehs here is the
REPO url only; the product rename is separate and still pending.
* feat(zchecksums): SHA-256 manifest so a download can be verified before it's run
CHECKSUMS.txt lists a SHA-256 for every top-level .ps1 and .cmd - the files a
user actually executes. zchecksums verifies them; zchecksums -Update
regenerates after an intentional edit.
The manifest is sha256sum format, so 'sha256sum -c CHECKSUMS.txt' works on
Linux/macOS/WSL as well as the PowerShell path on Windows. Hashes are identical
on every platform because .gitattributes pins .ps1/.cmd to CRLF everywhere -
that pin is now load-bearing, so it is commented as such.
Beyond changed and missing files it also reports a script that is on disk but
NOT in the manifest, so something added outside a commit still gets noticed.
Exits non-zero on any of the three.
Honest about its limits, in the header and the README: the manifest lives in
the same repo as the code, so it is an integrity check rather than a signature.
It catches a truncated clone, a forgotten local edit, or an unlisted file - not
a compromised repo.
CHECKSUMS.txt is pinned to LF: sha256sum treats a trailing CR as part of the
filename and would report every entry as missing on Linux.
tests/Checksums.Tests.ps1 keeps it from rotting - a stale manifest is worse
than none, since it either cries wolf until people ignore it or quietly stops
covering a new script. The tests assert the format, LF endings, sort order,
full coverage of on-disk scripts, current hashes, and that zchecksums itself
exits 1 on a tampered file (proved by appending a byte and restoring it).
* feat(zversion, zrelease): toolkit versioning + downloadable release zips
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).
The bash port and its bats suite are removed from the tree while they get more
testing. Everything they were referenced from is cleaned up so nothing dangles:
- README drops the 'Linux / macOS / WSL' pointer to bash/README.md (a dead
link once the folder is gone) and the aside that ZCONFIG is honoured by
both ports.
- CHANGELOG drops the two bash mentions.
- ZHelpers.ps1's ZCONFIG comment no longer cites zhelpers.sh.
- .gitattributes drops the now-dead bash/**, tests/bash/** and *.bats rules,
keeping the PowerShell CRLF rules.
No behaviour change to the PowerShell scripts; the Pester suite is untouched
and still passes 169/169.
Deliberately NOT a history rewrite: the port stays in this repo's history and
in full in the private mirror, so it can be restored with a revert when the
testing is done. Nothing here is secret - it is unfinished, not sensitive.
zstart only warns when a python project has no venv; zsetup is the
command that provisions one. For a python project it creates <root>/.venv
and installs deps; for vite/nextjs it runs npm install. Idempotent.
The pip install command comes from the project's optional "install"
config field (e.g. "-e backend" for deps in a subfolder, "-r reqs.txt"),
or is auto-detected from a root pyproject.toml/setup.py ("-e .") or
requirements.txt ("-r requirements.txt"). Bash + PowerShell + .cmd
wrapper, documented in the README and example configs.
Part of #26. The toolkit had no automated tests at all - including for the
functions that decide what goes into a deploy zip, which is where a dev .env
reached production (#23).
Phase 1 - the seam. Get-ZConfig read a hardcoded $PSScriptRoot\zconfig.json,
so nothing config-dependent could be tested without touching the real config.
Adds Get-ZConfigPath honoring $env:ZCONFIG (the bash port has always had this,
so it also closes a parity gap) and Reset-ZConfigCache to drop the memoised
config between fixtures. Deliberately did NOT convert the exit 1 paths to
throw: that changes observed CLI output, and the pure functions don't need it.
Phase 2 - 61 tests over the functions with no side effects: Get-ArchiveExcludes
(common/python/vite/nextjs lists, deploy.exclude merging, dedupe, array shape),
Get-ZConfig / Get-ZConfigPath / Get-ZProjectKeys / Get-ZProject (dash tolerance,
underscore-key filtering, memoisation), Get-ZEdgeProject, Get-RemoteComposeDir,
Get-Ec2Target / Get-Ec2Home, Get-LabelFromBuildJsonObj, Read-JsonBuildVersion.
The suite is verified by mutation testing rather than assumed useful - six
deliberate regressions were each introduced and confirmed to turn it red,
including reintroducing the exact #23 bug and its inverse (backups silently
dropping .env/uploads, which would produce restore points that cannot restore).
Runs off a fixture config injected via ZCONFIG, so it never reads a real
zconfig.json and passes on a machine that has never been configured.
* feat(zec2_rotatekeys): rotate/reset server-side secrets without exposing values
New tool for the leaked/overwritten prod .env case: -Rotate KEY regenerates a
key ON THE SERVER (openssl rand -hex 32) so the value never leaves the box;
-Set KEY takes an operator-known value from a masked prompt and streams it over
SSH stdin (never a command arg, never echoed). Backs the server .env up to a
timestamped .bak first, updates keys atomically (match-or-append), auto-detects
backend/.env from deploy.preserve, restarts only with -Restart, and -WhatIf
previews the plan. Docs added to README + CHANGELOG.
* fix(zec2_rotatekeys): recreate container on -Restart so the new .env loads
A plain 'docker compose restart' reuses the container's existing environment
and would NOT pick up env_file changes, leaving the app on the old secrets
after a rotation. -Restart now runs 'up -d --force-recreate <svc>', the
reliable way to apply the new .env. Docs updated to match.
* feat(zstart): support uvicorn/ASGI apps via a startApp config field
Python projects could only be started as `python -m <startModule>`, so
FastAPI/ASGI apps that run under uvicorn (like evo-ai:
`uvicorn app.main:app`) couldn't be started by zstart in either port.
Add an optional `startApp` field. When set, zstart runs
`uvicorn <startApp> --host <bind-host> --port <ports.dev> --reload` via
the venv python's -m (no PATH juggling), integrating zstart's existing
bind-host and dev-port handling. startApp takes precedence over
startModule; a python project still needs one or the other. Applied to
bash and PowerShell, documented in the README + example configs.
* feat(zstart): warn when falling back to system python (no project venv)
A python project with no .venv (or only a Windows .venv when on WSL)
silently ran under the system interpreter, which usually lacks the
project's deps - producing a cryptic ModuleNotFoundError far from the
cause. Now zstart prints a clear warning naming the missing venv and the
one-liner to create it, before starting. Bash + PowerShell.
* fix(bash): zstart --detached no longer hangs on the tracking FIFO
Detached mode forked the long-lived server while it still inherited the
ztokens tracking fds (the capture FIFO on 1/2, saved stdout/stderr on
3/4). The parent's EXIT-trap footer runs `tee` on that FIFO and waits for
EOF, which never came while the server held it open - so `zstart
--detached` (and zstartd / zrestart --detached) hung instead of
returning. detach() now redirects stdin<-/dev/null, stdout/stderr->log
and closes fd 3/4 before exec'ing the server. Verified on WSL: detached
returns in 0s and the server still boots.
* fix(zstart): git-pull pre-step can't hang on a credential prompt
start.gitPull ran `git pull --ff-only` before starting the server; in an
environment with no cached git credentials (e.g. WSL against an HTTPS
GitHub remote) git prompted "Username for 'https://github.com':" and the
whole start blocked on stdin. Run the pull with GIT_TERMINAL_PROMPT=0 so
it fails fast, log a clear "auto-pull skipped" note, and start with the
current checkout. Bash + PowerShell.
zkill now accepts 'all', expanding to every project that has a ports.dev
(edge/docker stacks with no local dev server are skipped) - matching
zdeploy all / zbackup all. Ported to both the PowerShell (ZKillOnly.ps1)
and bash (bash/zkill) versions; README + CHANGELOG updated.
* feat(bash): native bash port of all z-scripts for Linux/macOS/WSL
Full port: zhelpers.sh library (jq config, ssh, http/tcp, archive builder,
build-version, motd, port-kill), all commands (zstart/zkill/zrestart/zstop,
zdeploy with 5 kind handlers, zec2/zec2online/zrepair, zbackup/zbackup_ec2/
zsync/zbackup_and_sync, zstart_docker, zsetup_mail, setup_backup_schedule via
cron), Unix-path zconfig.example.json, and a bash/README.md.
Verified: bash -n clean on all scripts; archive exclusions, config semantics,
and zec2 tested against the real config and live server from Git Bash. Needs a
Linux/macOS/WSL shakedown for lsof/rsync/nohup paths before merging.
* chore: pin line endings (.gitattributes) - bash LF, powershell CRLF
* docs(readme): point Linux/macOS/WSL users at the bash port
* fix(bash): don't run the Windows venv python.exe on WSL/Linux/macOS
zstart's venv detection fell back to .venv/Scripts/python.exe (a Windows
binary) whenever it existed. On a Windows-built project accessed from WSL that
file sits on the mount and looks executable, so it got picked and failed with
'exec format error' instead of falling through to python3. Guard that branch to
Windows-family shells (msys/cygwin), where a .exe can actually run.
Verified on WSL: zstart --detached now backgrounds a stdlib app via python3,
serves HTTP 200, logs to /tmp/zstart-<key>.log, and zkill terminates it and
frees the port.
* feat(bash): add token-usage tracking to the bash port (#6)
Adds z_track_start/z_track_stop + z_record to zhelpers.sh and wires
z_track_start into every command script. Each run now captures its own output
volume (FIFO+tee, ANSI stripped), prints the '--- N lines / N chars / ~N tokens
est. (Claude Code) ---' footer, and appends one JSONL row per top-level run in
the same shape as the PowerShell tokens.jsonl. A nested-run guard keeps
zrestart from double-counting its zkill/zstart children.
Data dir precedence: $ZTOKENS_DATA, config ztokens.dataDir, sibling
../../ztokens/data, else ~/.ztokens/data. Docs + example config updated.
Verified on WSL (isolated data dir): single run records correctly; nested
zrestart produces one combined record, not three; est = round(chars/3.5).
The project-directory replacement preserved only ./.env, silently
destroying every other server-side file (.env.db, staged signing keys,
certs) on every deploy — and the vite kind preserved nothing at all.
- preserve all .env* files at the project root by default
- new deploy.preserve array for additional files/directories
- implemented via tar to the home dir before the wipe, extract after
the unzip; server-side copies win over zip contents (same semantics
./.env always had)
- helpers deliberately avoid embedded quotes and $( ): PowerShell 5.1
strips embedded double quotes when passing args to ssh.exe, which
silently corrupts remote commands (discovered when v1 of this fix
failed exactly that way)
Fixes#2
Projects not published through the edge proxy had a false-PASS problem:
the fallback reachability check hit http://<server-ip>/, which the
proxy's default vhost happily answers for apps that never started.
- new Test-DeployHealth: checks the app FROM the server over SSH
(curl localhost:<port><path>), optional expected substring
- opt in per project: "verify": { "port", "path", "expect" }
- projects with neither domain nor verify are reported NOT verified
instead of green-lighting the proxy's default page
- example config + README + changelog updated
Per-run captures are measured; the ~26,500/day total multiplies them by assumed
typical run counts (zdeploy/zrestart at 10-15/day dominate). Label that boundary
explicitly in README, ELEVATOR_PITCH, and TOKEN_SAVINGS so the daily figure isn't
read as a direct measurement.
- One headline number everywhere: ~26,500 measured tokens/day (README said 3,000-7,000; ELEVATOR_PITCH said 157,000-540,000)
- Measurement note: measured output volume, estimated tokenization; /3.5 is conservative for code-heavy output
- Raw-orchestration column explicitly labeled an upper bound, not a prediction
- Measured dollar column priced at input rates (blended kept for est-raw only); note on re-sent context tokens
- Untrack md/Z-ScriptTokenData.md (superseded internal notes; md/ gitignored)
Config-driven PowerShell scripts to run infrastructure tasks (deploy, restart, backup, diagnostics) yourself instead of having an AI agent orchestrate them, to save agent tokens. Environment specifics live in zconfig.json (gitignored).