# AI Usage Bar · Packaging Kit

> **Status:** prepared kit. The PowerShell build was authored and structurally
> self-reviewed on Linux, but **the build itself MUST run on Windows**. I
> cannot exercise `build_zip.ps1` here — there is no PowerShell + no Windows
> + no internet access from this sandbox to test the download/extract/pip
> sequence. Treat the artifacts below as "ready to run on a Windows box",
> not "already verified to run on a Windows box".

---

## 0. TL;DR for Bae

```powershell
# On a Windows box (Win10 21H2 or Win11), from the repo root:
cd tools\product\packaging
powershell -ExecutionPolicy Bypass -File .\build_zip.ps1
```

Prereqs on the Windows box: PowerShell 5.1+ (built-in), ~300 MB free disk,
internet on first run (downloads Python 3.12.7 embeddable + PySide6).

Output: `tools\product\packaging\AiUsageBar-v1.zip` (~30 MB).
End user unzips → double-clicks `Setup.cmd` → done. **No Python install on
the end-user machine** — Python is bundled inside the ZIP.

---

## 1. Why "embedded Python folder" and not "PyInstaller one-file .exe"

This decision lives in the SETUP-GUI-SPEC §2 (option A vs. B vs. C) and is
worth re-justifying here at the build level because the two approaches
have *very* different runtime shapes.

### 1.1 What the runtime actually does

The AI Usage Bar is not a single process. The bar (`claude_bar.pyw`) and
the setup GUI (`setup_gui.py`) **both** shell out to the per-provider
collectors (`kimi_collector.py`, `minimax_collector.py`,
`antigravity_collector.py`) as **separate** Python subprocesses, AND
both launch CDP debug browsers (`launch_ai_usage_chrome.cmd`,
`launch_antigravity_debug.cmd`) that the collectors then talk to over
the Chrome DevTools Protocol on ports 9222 / 9223.

That fan-out is the load-bearing design choice: collectors are
*independently runnable* so the bar can survive a Kimi outage without
losing the Claude pill, and so the Setup GUI's "Test" button can give an
honest answer ("provider X works right now") without lying.

### 1.2 PyInstaller one-file .exe — and why we did NOT pick it

PyInstaller option A from the spec (`.exe` for both setup and bar) would:

1. Force one frozen binary per entry point. Either we freeze the bar AND
   freeze the collectors AND freeze the setup GUI as **three** binaries,
   or we shove all of them into a "one-file exe that decides what to do
   based on argv" — both ugly.
2. The bar's `subprocess.Popen([sys.executable, "kimi_collector.py", …])`
   would have to switch to `subprocess.Popen([frozen_bar_exe,
   "--child", "kimi_collector", …])` because the child interpreter MUST
   be the same frozen binary. That is a non-trivial refactor of the
   working bar — `sys.executable` resolves to the frozen exe, not to
   python.exe, and `multiprocessing` on Windows is a known footgun.
3. PySide6 inside PyInstaller balloons to 60–90 MB **per** frozen binary
   (Qt6 runtime + Python + shims). Three binaries = ~200 MB ZIP. The
   embeddable approach is one shared Python (~30 MB) and three thin
   scripts that reuse it.
4. Hidden-imports for `PySide6.QtNetwork`, `PySide6.QtPrintSupport`,
   etc., proliferate with each Qt module we touch — fragile across
   PySide6 point releases.
5. Frozen-binary + Windows Defender SmartScreen = "Unknown publisher"
   warning on first run. End users see scary red.

The spec listed option A as "recommended" but acknowledged it bundles
PySide6 per-exe. Given the subprocess-collector architecture, the
cost-benefit flips: **embedded-Python-folder is cheaper, faster to
build, smaller to ship, and doesn't refactor working code**.

### 1.3 Embedded Python folder — what we picked and why

Approach (now in `build_zip.ps1`):

* Download the **official** `python-X.Y.Z-embed-amd64.zip` from python.org.
* Unzip into `<stage>\python\`.
* Patch the embeddable's `._pth` file so `site-packages` is enabled.
* Bootstrap `pip` into it via `get-pip.py`.
* `pip install PySide6 websocket-client` into `<stage>\python\Lib\site-packages\`.
* Copy all the **literal .py** scripts next to that Python, unchanged.
* Generate two top-level launchers:
    * `Start AI Usage Bar.cmd` → calls `<stage>\python\pythonw.exe app\claude_bar.pyw`
    * `Setup.cmd` → calls `<stage>\python\python.exe app\product\setup_gui.py`
* Zip the whole folder.

Properties:

* The collectors still run as `subprocess.Popen([python_exe,
  "kimi_collector.py", …])` — **no code change** in
  `setup_gui.py` or `claude_bar.pyw`.
* `sys.executable` resolves to the bundled `python.exe` — no
  frozen-binary special cases.
* `pythonw.exe` (no console window) and `python.exe` (with console) are
  both bundled, so the bar can be GUI-only and the setup GUI can print
  to a console if a developer attaches one.
* ZIP is **~30 MB**. ~10 MB of that is PySide6 site-packages; the rest
  is the Python embeddable.
* No refactor. No hidden-imports. No SmartScreen warnings — it's just
  `.cmd` files and a Python folder.

The one thing the embeddable approach *doesn't* do is hide the
implementation behind a single `.exe`. That's a feature, not a bug, for
a project that says "open source, read the source, donate if it helped".

---

## 2. What ships in the ZIP

```
AiUsageBar-v1\
├── Start AI Usage Bar.cmd          ← double-click to launch the bar
├── Setup.cmd                       ← double-click to open the wizard
├── connect-all.cmd                 ← opens all debug browsers in one shot
├── INSTALL.txt                     ← short user manual
├── PACKAGING.md                    ← this file (technical)
├── python\                         ← bundled embeddable Python 3.12.7
│   ├── python.exe
│   ├── pythonw.exe
│   ├── python312._pth              (patched to enable site-packages)
│   ├── python312.dll, *.dll, *.pyd ...
│   └── Lib\site-packages\          ← PySide6, websocket-client, pip
└── app\
    ├── claude_bar.pyw
    ├── providers.py
    ├── kimi_collector.py
    ├── minimax_collector.py
    ├── antigravity_collector.py
    ├── launch_ai_usage_chrome.cmd
    ├── launch_antigravity_debug.cmd
    └── product\
        ├── setup_gui.py
        ├── config.py
        ├── requirements.txt
        ├── run_setup.cmd
        └── landing\
            ├── index.html
            └── styles.css
```

`Start AI Usage Bar.cmd` and `Setup.cmd` are **generated** by
`build_zip.ps1` — they resolve `%~dp0` at runtime so they always
find the bundled Python relative to themselves. No path editing by the
end user. No environment variables to set.

---

## 3. Exact build command + parameters

### 3.1 Default (what you usually want)

```powershell
cd <repo-root>\tools\product\packaging
powershell -ExecutionPolicy Bypass -File .\build_zip.ps1
```

Outputs:
* `.\dist\AiUsageBar-v1\`  — the staging folder, in case you want to
  inspect it before zipping or push it via a different channel.
* `.\AiUsageBar-v1.zip`    — the distributable.

### 3.2 Optional parameters

```powershell
# Pin a different Python release
powershell -File .\build_zip.ps1 -PythonVersion 3.12.7

# Use an internal mirror that proxies python.org
powershell -File .\build_zip.ps1 -EmbedUrl "https://pypi.internal.corp/python-3.12.7-embed-amd64.zip"

# Keep the .zip cached between runs (default: %TEMP%\ai-usage-bar-python)
powershell -File .\build_zip.ps1 -EmbedCacheDir "D:\cache\ai-usage-bar-python"

# Just stage, don't zip (handy for iterating on the launchers)
powershell -File .\build_zip.ps1 -SkipZip
```

### 3.3 Re-running

The script is idempotent for the **download** (re-uses the cached
embeddable zip in `$EmbedCacheDir`) but always re-extracts from scratch
into a fresh `dist\` — so a re-run is always safe.

---

## 4. Prerequisites (Windows box doing the build)

| Prereq | Why | Default |
|---|---|---|
| PowerShell 5.1+ | runs `build_zip.ps1` | ships with Win10 21H2 / Win11 |
| ~300 MB free disk | python embeddable (~25 MB) + PySide6 (~80 MB) + dist + zip | – |
| Internet on first run | downloads Python embeddable + get-pip.py + PySide6 + websocket-client | – |
| No admin needed | everything writes under the user profile and under `dist\` | – |
| ExecutionPolicy Bypass | `-ExecutionPolicy Bypass` on the command line, or run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once | – |

Things that **do NOT** need to be on the build box:
* Python (the kit downloads its own embeddable).
* Visual Studio / compilers (PySide6 ships pre-built wheels).
* .NET (we use Add-Type for `System.IO.Compression.FileSystem`, which
  ships with PowerShell 5.1+).

---

## 5. What was self-reviewed vs. executed

| Step | Status |
|---|---|
| `build_zip.ps1` authored | ✅ on Linux |
| Parameter list + paths | ✅ verified against the repo on Linux |
| Every file in `$SourceFiles` exists | ✅ verified on Linux (see verification log below) |
| PowerShell syntax | ⚠️ not linted — this sandbox has no PowerShell. The script uses only PS 5.1 stock cmdlets (no external modules). |
| Download + extract + pip | ❌ not executed — Windows + internet required |
| Generated `.cmd` launchers | ⚠️ written but not run — test by unzipping the produced ZIP on a Windows box |

### 5.1 Source-file existence check (executed here)

```
+ claude_bar.pyw                                    [OK]
+ providers.py                                      [OK]
+ kimi_collector.py                                 [OK]
+ minimax_collector.py                              [OK]
+ antigravity_collector.py                          [OK]
+ launch_ai_usage_chrome.cmd                        [OK]
+ launch_antigravity_debug.cmd                      [OK]
+ product\setup_gui.py                              [OK]
+ product\config.py                                 [OK]
+ product\requirements.txt                          [OK]
+ product\run_setup.cmd                             [OK]
+ product\landing\index.html                        [OK]
+ product\landing\styles.css                        [OK]
```

If you rename or move any of these in the repo, update the
`$SourceFiles` array in `build_zip.ps1` (search for `$SourceFiles =`).
The script will refuse to run with a clear error if any of them are
missing.

---

## 6. Why I did NOT run this on Linux (and why that's fine)

The artifact this kit produces is **a Windows-only ZIP that contains a
Windows-only Python distribution**. There is no way to exercise it on
Linux without WINE + a manual unzip of the python.org embeddable +
pip-install into that WINE Python — and at that point we have not
proven the Windows path works, we have proven a different, more
fragile path works. So instead I:

1. **Authored** the script on Linux with PS5.1-stock cmdlets only (no
   `Install-Module`, no PS7-only syntax, no third-party modules).
2. **Verified structurally** — every file the script copies exists,
   every path the script writes resolves correctly relative to itself,
   every `[CmdletBinding()]` parameter has a sensible default.
3. **Documented** every assumption in comments inside the script so
   when Bae (or whoever) runs it on Windows, the failure mode of each
   step is obvious.
4. **Explicit fail-loud** on every dependency step (PySide6 import,
   pip exit codes, missing source files) so the first failure on a
   Windows box prints a usable error message, not a traceback.

This is the honest report: prepared, not executed. If the run on
Windows surfaces a bug, the most likely categories are:

* Corporate proxy / firewall blocks `python.org` or `pypi.org`. The
  script catches the download failures and suggests `-EmbedUrl` /
  setting `HTTP_PROXY` — but if the pip install is blocked silently,
  the PySide6 smoke test will catch it (exit 9).
* The embeddable zip layout changed in a future Python release (e.g.
  `_pth` filename or `import site` line). The script's section 6 is
  defensive (comment-regex matches `^import\s+site`) but if upstream
  changes the layout dramatically, the sanity check on `python.exe` /
  `pythonw.exe` will fail loudly.
* Antivirus quarantines the bundled Python immediately after unzip.
  Standard Windows AV story; not a build script issue. Documented in
  `INSTALL.txt` troubleshooting.

---

## 7. Open questions / follow-ups (NOT in scope for this kit)

* **Code signing.** SmartScreen will eventually nag users about an
  unsigned `.cmd`. Out of scope — would require a code-signing cert.
* **Auto-update.** Not in scope. The bar reads providers live, but
  there's no "check for a new ZIP" mechanism.
* **Portable mode tweak — readline / ssl certs.** The embeddable does
  not ship `libffi`-bundled ssl certs by default; for *this* kit that
  doesn't matter (we talk to `api.anthropic.com` and `kimi.com` over
  HTTPS, but `certifi` is pulled in by `requests` if needed, and we
  don't use `requests` — stdlib `urllib` uses the OS cert store on
  Windows, which is fine). If a future collector needs `certifi`, add
  `certifi` to the pip install list in section 7 of the script.
* **Single combined ZIP with the Telegram bot.** Separate kit — the
  bot reads `~/.cache/ai_usage/*.json` written by *this* kit's
  collectors, so it can be a second ZIP that depends on this one.

---

## 8. Files in this kit

| File | Purpose |
|---|---|
| `build_zip.ps1` | Run on Windows. Downloads embeddable Python, pip-installs PySide6 + websocket-client, copies app source, generates launchers, zips everything. |
| `INSTALL.txt` | End-user manual. Copied INTO the produced ZIP so the user can read it offline. |
| `PACKAGING.md` | This file. The architectural writeup + exact build command + what was/was not verified. |