# Package the macOS app

> npm run package compiles runtimes, patches Settings Router, replaces app.asar, sets reconstructed identity, ad-hoc signs, and verifies.

- Repository: sashimikun/grok-bot-0.18-reconstructed
- GitHub: https://github.com/sashimikun/grok-bot-0.18-reconstructed
- Human docs: https://grok-wiki.com/public/docs/sashimikun-grok-bot-0-18-reconstructed-c774cc9a5c15
- Complete Markdown: https://grok-wiki.com/public/docs/sashimikun-grok-bot-0-18-reconstructed-c774cc9a5c15/llms-full.txt

## Source Files

- `scripts/package-macos.mjs`
- `scripts/clean-build.mjs`
- `scripts/lib/codesign.mjs`
- `scripts/lib/macos-package-verification.mjs`
- `scripts/lib/build-asar.mjs`
- `scripts/lib/router-renderer-patch.mjs`
- `tests/publication-packaging.test.mjs`

---

---
title: "Package the macOS app"
description: "npm run package compiles runtimes, patches Settings Router, replaces app.asar, sets reconstructed identity, ad-hoc signs, and verifies."
---

`npm run package` runs `npm run check`, then `node scripts/package-macos.mjs`. That script aborts unless `process.platform === "darwin"`, builds a **fidelity-hybrid** ASAR (`buildKind: "fidelity-hybrid-reconstruction"`), copies the checksum-pinned 0.18 Electron shell with `/usr/bin/ditto`, replaces `Contents/Resources/app.asar` and `app.asar.unpacked`, rewrites reconstructed identity in `Info.plist`, ad-hoc signs with `codesign --sign -`, and verifies the result.

Default output:

```text
dist/Grok Bot 0.18 Reconstructed.app
```

The official cached `Grok Bot.app` is **reference-only**. Packaging copies its ABI-matched shell and helpers, then must **not** keep the official signed `Contents/MacOS/Grok Bot` identity or the official `app.asar` SHA-256.

<Warning>
Packaging is macOS Apple Silicon only. On any other platform the script throws: `The reconstructed macOS application can only be packaged on macOS.`
</Warning>

## Prerequisites

<Steps>
<Step title="Install the toolchain">
Apple Silicon macOS, Node `>=26.5.0 <27`, Xcode Command Line Tools (`/usr/bin/codesign`, `/usr/bin/ditto`, `/usr/bin/plutil`, `/usr/bin/xattr`), and Git LFS.
</Step>
<Step title="Hydrate the pinned 0.18 runtime">
Run `npm run bootstrap` so the cached app, official `app.asar`, and ignored `src/app/dist` renderer exist and match the pinned SHA-256 identities.
</Step>
<Step title="Install and typecheck">
`npm ci` then `npm run check` (the package script already runs check first: frontend typecheck, `source/` typecheck, and `tests/*.test.mjs`).
</Step>
</Steps>

## Command

```sh
npm run package
```

Equivalent:

```sh
npm run check && node scripts/package-macos.mjs
```

Success log (counts come from `runtime-deps-manifest.json` and the unpacked runtime snapshot):

```text
Packaged application: dist/Grok Bot 0.18 Reconstructed.app (<n> native manifest entries, <m> unpacked runtime files)
```

Override the `.app` basename with `GROK_BOT_OUTPUT_APP_NAME` (the value is passed through `path.basename`). Leave `CFBundleName` / `CFBundleExecutable` as `Grok Bot`: Electron derives nested helper names from that string, and this fork reuses the ABI-matched 0.18 runtime.

<ParamField body="GROK_BOT_OUTPUT_APP_NAME" type="string">
Optional. Basename of the bundle under `dist/`. Default: `Grok Bot 0.18 Reconstructed.app`.
</ParamField>

<ParamField body="GROK_BOT_HOST_BINDINGS_MANIFEST" type="string">
Optional. Schema version 1 host production-binding manifest. Unbound slots keep host in `artifact-fallback` mode; no inferred native fallback.
</ParamField>

<ParamField body="GROK_BOT_ELECTRON_MAIN_BINDINGS_MANIFEST" type="string">
Optional override. If unset and `manifests/reconstruction/electron-main-production-bindings-manifest.json` exists, that path is used. Unbound slots keep Electron main in `artifact-fallback`.
</ParamField>

<ParamField body="GROK_BOT_RENDERER_SOURCE" type="string">
Optional repo-relative directory with `index.html`. Replaces staged `dist/renderer` during ASAR staging. Default packaging does **not** use `frontend/` Vite output; it keeps the checksum-pinned shipped renderer plus the Settings Router patch.
</ParamField>

## Pipeline

```mermaid
flowchart TB
  subgraph gate [Gate]
    CHK["npm run check"]
    DAR["darwin only"]
  end
  subgraph compile [Clean-source compile]
    SRC["source/*.ts"]
    FID[".build/fidelity-clean-runtime"]
    ACT["host / electron-main activation"]
  end
  subgraph renderer [Renderer authority]
    ART["src/app/dist/renderer"]
    PROV["dist/renderer-artifact-provenance.json"]
    PATCH["applyOriginalRendererRouterPatch"]
    EXT["dist/renderer-router-extension.json"]
  end
  subgraph shell [Official 0.18 shell]
    RT["cached Grok Bot.app"]
    DITTO["ditto + xattr -cr"]
  end
  subgraph identity [Reconstructed identity]
    ASAR["replace app.asar + unpacked"]
    PLIST["com.anysphere.sand.reconstructed"]
    SIGN["codesign --sign -"]
    VER["verify shell, unpacked runtime, signature"]
  end
  CHK --> DAR --> SRC --> FID --> ACT
  ART --> PROV
  FID --> PATCH
  ART --> PATCH --> EXT
  RT --> DITTO --> ASAR --> PLIST --> SIGN --> VER
  EXT --> ASAR
  ACT --> ASAR
```

### 1. Compile reconstructed runtimes

`buildFidelityReconstructedAsar()` stages the bootstrapped app tree, then overlays a fidelity clean distribution:

| Runtime | Packaged path | Default mode |
| --- | --- | --- |
| electron-main | `dist/electron-main/main.cjs` | `artifact-fallback` unless production bindings validate, then `clean-source` |
| host | `dist/host/host-main.cjs` | same activation rule |
| primary / dev-controls / webview / VNC preloads | `dist/electron-preload/*.cjs` | `clean-source` |
| node-agent-coordinator | `dist/node-agent-coordinator/main.cjs` | `clean-source` |
| host workers | agent-store, transcript-mirror, box-store-vacuum, search-index | `clean-source` |
| box-exec-daemon / local-exec-daemon | `dist/box-exec-daemon/main.cjs`, `dist/local-exec-daemon/main.cjs` | `clean-source` |
| renderer | `dist/renderer` | `checksum-pinned-artifact-runtime` |
| electron-runtime-dependencies | `dist/deps` | `artifact-runtime` (copied from 0.18 unpacked) |
| node-runtime-dependencies | `dist/node-deps` | `generated-runtime` (local Node ABI tree-sitter rebuild) |
| native-runtime-tools | `dist/native` | `artifact-runtime` |
| electron-shell | `Contents/Frameworks/Electron Framework.framework` | `artifact-runtime` |

`dist/deps/node_modules/node-gyp-build` is a byte-exact sibling copy recorded as `resolutionClosure.mode: "byte-exact-sibling-package-copy"` in `dist/deps/runtime-deps-manifest.json`.

The composition audit is packed at `dist/runtime-composition-audit.json`. `dist/reconstruction-build.json` is rewritten to `schemaVersion: 2` after the audit is attached.

Clean-source executables carry a `// Deterministic clean-source` banner. Artifact-fallback Electron main is the immutable evidence bundle plus an explicit reconstructed transform (updater/telemetry guard and optional `GROK_BOT_RECONSTRUCTED_DEV` seams). When Electron main activates as `clean-source` with `reconstructedPackage: true`, the same updater guard is prepended to the bundled `main.cjs`.

### 2. Patch Settings Router into the shipped renderer

Default packaging does **not** swap in the Vite `frontend/` workspace. It copies the exact 0.18 renderer from `src/app/dist/renderer`, then `applyOriginalRendererRouterPatch` edits **exactly one** settings-registry chunk and **exactly one** settings-panel chunk.

Transforms:

| Transformation | Effect |
| --- | --- |
| `settings-registry` | Inserts `{id:"router",label:"Router",icon:"git-branch"}` between General and Usage |
| `router-panel` | Inserts `RRouterPanel` / `RBoxRuntime` and wires `x==="router"` |
| `usage-panel` | Routes Usage to `RRouterUsage` (Cursor usage UI remains when provider is `cursor`) |

Provider ids in the injected UI: `cursor`, `claude-code`, `codex`, `openrouter`. The panel talks to `window.desktop.agent.getInferenceRouter` / `setInferenceRouter`, `getBoxRuntime` / `setBoxRuntime`, and `window.desktop.secrets.list` / `upsert`. Missing or ambiguous minified anchors throw: `Original renderer <label> anchor is missing or ambiguous.` or `Expected one original Settings registry and panel chunk`.

The patch writes `dist/renderer-router-extension.json`:

- `schemaVersion`: `1`
- `mode`: `original-renderer-settings-extension`
- `features`: `settings-router-provider`, `settings-local-docker-vm`, `usage-current-provider`
- `chunks[]`: `role`, `path`, original and patched `{ bytes, sha256 }`

Renderer provenance `dist/renderer-artifact-provenance.json` stays bound to upstream `app.asar` SHA-256 `6665408168466f9cacc6087e917890c17f59d2e2e9c2404a5c4a59ad79c1de58`. Byte checks that **allow** the Settings edit consume the extension record (`verifyChecksumPinnedRendererPackage`, used by `npm run package:diagnostic`).

### 3. Replace ASAR in the official shell

After `verifyOfficialMacReference` confirms the cached app’s `Contents/MacOS/Grok Bot` and `Contents/Resources/app.asar` hashes:

1. `ditto` the official app onto `dist/Grok Bot 0.18 Reconstructed.app` (after deleting any previous output).
2. `xattr -cr` the copy so Gatekeeper does not reject the new ad-hoc signature because of DMG quarantine.
3. Delete the copied `app.asar` and `app.asar.unpacked`.
4. Copy `.build/fidelity/app.asar` and `.build/fidelity/app.asar.unpacked`.

Unpacked trees that must stay byte-identical to the staged ASAR unpack: `dist/deps`, `dist/native`, `dist/node-deps`. `dist/host/extensions/codebase-telemetry/csnaps` must **not** be packaged.

### 4. Reconstructed identity

`plutil` updates `Contents/Info.plist`:

| Key | Value |
| --- | --- |
| `CFBundleIdentifier` | `com.anysphere.sand.reconstructed` |
| `CFBundleDisplayName` | `Grok Bot 0.18 Reconstructed` |
| `CFBundleURLTypes` | single Viewer entry, scheme `sand` only (`Grok Bot reconstructed auth callback`) |
| `ElectronAsarIntegrity` | **removed** (stale official integrity would reject the new archive) |
| `CFBundleName` / `CFBundleExecutable` | left as `Grok Bot` |

Inherited URL aliases such as `grokbot` are stripped. The official bundle on disk is never rewritten.

### 5. Ad-hoc sign and verify

`Contents/_CodeSignature` is removed, then:

```text
/usr/bin/codesign --force --deep --timestamp=none --sign - <app>
```

Identity `-` is ad-hoc. Stdio is non-interactive (`ignore` / `inherit` / `inherit`). If the first pass fails (nested framework signature still held after the copied runtime was in use), the script warns and retries **once**. Then:

```text
/usr/bin/codesign --verify --deep --strict <app>
```

`verifyReconstructedMacPackage` then requires:

- official shell and `app.asar` still match the pinned 0.18 hashes
- reconstructed Mach-O matches the official binary **after** signature exclusion, and is **not** a copy of the official signed shell
- reconstructed `app.asar` SHA-256 is **not** the official archive hash
- unpacked runtime files and `runtime-deps-manifest.json` are byte-identical to the staged unpack (`platform: darwin`, `arch: arm64`)

## Updater and telemetry seams

Packaged Electron main always prepends (idempotent):

```js
process.env.SAND_DISABLE_UPDATES ??= "1";
process.env.SAND_DISABLE_SENTRY ??= "1";
process.env.SAND_DISABLE_TELEMETRY ??= "1";
```

Explicit environment values still win. These defaults are a packaging-boundary guard, not a runtime settings toggle.

## Outputs

:::files
dist/
  Grok Bot 0.18 Reconstructed.app/
    Contents/
      Info.plist
      MacOS/Grok Bot
      Resources/
        app.asar
        app.asar.unpacked/
          dist/deps/
          dist/native/
          dist/node-deps/
.build/
  fidelity/
    app/
    app.asar
    app.asar.unpacked/
  fidelity-clean-runtime/
:::

Required ASAR entries (also checked by `npm run verify`):

`dist/electron-main/main.cjs`, preload set, `dist/node-agent-coordinator/main.cjs`, `dist/host/host-main.cjs` plus four host workers, `dist/local-exec-daemon/main.cjs`, `dist/renderer/index.html`, `dist/reconstruction-build.json`, `dist/runtime-composition-audit.json`, `package.json`.

Unpacked natives that must exist: `dist/deps/better-sqlite3/build/Release/better_sqlite3.node`, `dist/native/sand-webauthn-signer`, and generated tree-sitter Node bindings under `dist/node-deps`.

When host or Electron main remain `artifact-fallback`, recovered bundles `dist/recovered-source/host/host-main.cjs` and `dist/recovered-source/electron-main/main.cjs` must be present. Clean activation **removes** those fallbacks from the archive.

## Related commands

| Script | Role |
| --- | --- |
| `npm run bootstrap` | Pin DMG / `app.asar`, cache the shell, hydrate `src/app/dist` |
| `npm run check` | Typecheck + unit tests; always runs before package |
| `npm run package:diagnostic` | Same fidelity ASAR with renderer-extension-aware checksum verification; writes a uniquely named diagnostic `.app` |
| `npm run verify` | Inspect a packaged `.app` (default `dist/Grok Bot 0.18 Reconstructed.app`, or `--app <path>`): ASAR listing, composition audit, bundle id, `sand` URL, leftover `ElectronAsarIntegrity`, codesign |
| `npm run smoke` | Native e2e against the packaged app |

<Note>
`npm run package` does **not** invoke `scripts/verify.mjs`. It verifies official hashes, the reconstructed shell invariant, unpacked runtime bytes, and `codesign --verify`. `npm run verify` additionally walks ASAR membership and `reconstruction-build.json`. Its checksum-pinned renderer loop hashes packaged files against the **original** provenance inventory and does not apply `renderer-router-extension.json`; use `verifyChecksumPinnedRendererPackage` / `npm run package:diagnostic` for patch-aware renderer bytes.
</Note>

## Failure modes

| Symptom | Cause |
| --- | --- |
| `can only be packaged on macOS` | Non-darwin host |
| Official shell / `app.asar` hash mismatch | Bootstrap cache drifted; re-run bootstrap |
| Missing Settings registry/panel chunk | Shipped renderer anchors changed or `GROK_BOT_RENDERER_SOURCE` is not the 0.18 bundle |
| Ad-hoc sign fails twice | Nested framework still in use; quit the app and retry |
| Gatekeeper rejects launch | Quarantine xattrs not cleared (`xattr -cr` is required after `ditto`) |
| `Reconstructed package must not copy the official app.asar` | Replacement copy failed |
| Missing unpacked `dist/deps` / `dist/native` / `dist/node-deps` | Integrity unpack incomplete |
| Stale `ElectronAsarIntegrity` | `plutil -remove` did not run |

Do not weaken checksum, bundle-id, or signing gates to make a build pass.

## Next

<CardGroup>
<Card title="Bootstrap the pinned runtime" href="/bootstrap-pinned-runtime">
Hydrate the 0.18 DMG, shell, and `src/app/dist` before packaging.
</Card>
<Card title="Packaging failures" href="/packaging-failures">
Non-darwin abort, missing renderer anchors, codesign retry, quarantine, required ASAR entries.
</Card>
<Card title="Verify and smoke" href="/verify-and-smoke">
Post-package ASAR membership, evidence markers, and native e2e.
</Card>
<Card title="Reconstruction boundary" href="/reconstruction-boundary">
Clean-source vs checksum-pinned renderer vs `com.anysphere.sand.reconstructed`.
</Card>
<Card title="Work on the frontend reconstruction" href="/edit-frontend-workspace">
`frontend/` is a Vite design workspace; packaged UI stays the patched shipped renderer unless `GROK_BOT_RENDERER_SOURCE` overrides it.
</Card>
<Card title="Environment variables" href="/environment-variables">
`GROK_BOT_OUTPUT_APP_NAME`, binding manifests, `SAND_DISABLE_*`, renderer override.
</Card>
</CardGroup>
