Originally created by: Akarsh-Hegde
First slice of Gap-2 Bucket 1 (self-contained DMG, the $0/unblocked part). Tauri bundle.resources now copies the daemon + a11y-helper binaries and their launchd plists into Meridian.app/Contents/Resources/backend/, so a first-run install can register them from inside the .app — eliminating npm install -g screenpipe / brew install ffmpeg for these pieces.
.app contains Contents/Resources/backend/: meridian (15M daemon), meridian-a11y-helper, com.meridiona.daemon.plist, com.meridiona.a11y-helper.plist.Meridian_0.1.0_aarch64.dmg 16 MB (was 10 MB; the +6 is the bundled backend).cargo build --release artifact, which must run before tauri build. The release .releaserc prepareCmd already does this; build it by hand for a local bundle.First-run install orchestration: on first launch, copy the bundled binaries → ~/.meridian/bin and register the launchd agents (port install-from-bundle.sh; SMAppService for the non-capture services). This PR is just the bundling foundation.
Decisions/Gap-2 scope - bundle the backend into the .app.Stacked on feat/mlx-runtime-integrity (#320) → … → spike → main.
🤖 Generated with Claude Code
Tickets: #322
Tickets: #323
Tickets: #330
Tickets: #347
Tickets: #351
Originally posted by: Akarsh-Hegde
Added the parallel agent's oauth bugfix to this PR.
f6b8781— fix(oauth): prevent GITHUB_TOKEN from merging onto prior .env line (cherry-picked from local-stagingc88919d, authored by a parallel agent).upsert_env_keyhad two bugs insrc/intelligence/oauth/github.rs: the replace path dropped the trailing newline that.lines()strips, and the append path wrote the new key immediately after the last byte without a separator — so keys could merge onto the previous.envline when the file had no trailing newline. Fix preserves the trailing newline on replace and prepends\nbefore append when needed.It had been sitting unpushed on local
staging; rather than push it upstaging(which is the test-only channel under Option B), it's folded into this PR so it rides the normalfeature → mlx-runtime chain → … → mainreview path. Orthogonal to the bundling change (touches onlyoauth/github.rs), cherry-picked clean, full pre-push suite green.Originally posted by: Akarsh-Hegde
Slice 1b — first-run install of the bundled backend (
56f7468)Builds on slice 1a (which bundled the backend into
Resources/backend/). This is the tray side that stages + registers it on the.appDMG path, so the DMG needs no shell installer.New module
backend_install.rs—ensure_backend_installed(), spawned off the Taurisetuphook:.app(noResources/backendundertauri dev/source → those keep the shell scripts) and the bundled daemon's SHA-256 differs from the last install (first run / post-update).meridian+meridian-a11y-helper→ stable~/.meridian/bin/(cmp-guarded copy +chmod 0755), renders the two plists, and runs the launchctlbootout → bootstrap → enable → kickstartdance — a faithful port ofinstall-from-bundle.sh, not SMAppService (the a11y grant is code-hash-keyed and must run from a stable path outside the re-signed.app; SMAppService's Login-Items payoff waits for Dev-ID signing, Bucket 3).Env unification: DMG daemon
WorkingDirectory=~/.meridian→dotenvyself-loads the canonical~/.meridian/.envthe tray already writes (OTLP left empty in the plist so the daemon self-loads it — a baked value would go stale).Ripple (same commit):
install.rsmeridian_bin()now prefers~/.meridian/bin/meridian; CLAUDE.md daemon-config gotcha corrected (the old~/.meridian/app/.envclaim was stale — that file is no longer written) + DMG case documented.Tests:
apply_subssubstitution + a guard that the real committed plist templates fully substitute (XML comments stripped) so a new body placeholder can't ship a broken plist.cargo test+ full pre-push suite green.Verification note: live launchd registration is intentionally not run on a dev machine (would clobber an existing a11y plist / bootstrap a duplicate daemon against
meridian.db). It's deferred to the staging DMG E2E pass on a clean account: launch →launchctl print gui/$(id -u)/com.meridiona.daemonloaded →~/.meridian/logs/daemon.logshows the poll loop →a11y-helper.logshows trust state → second launch is a no-op.Originally posted by: Akarsh-Hegde
Teardown slice —
meridian uninstall(036c59a)The inverse of slice 1b's install. Dragging
Meridian.appto the Trash leaves the launchd agents 1b registers running forever (no app code runs once the bundle is gone). This subcommand lives in the daemon binary (staged at~/.meridian/bin/meridian), so it survives the.appbeing trashed and can clean up its own orphans — which a tray menu item couldn't.Turns out SETUP.md already documented
meridian uninstall("stop services and remove the CLI") but the native binary never implemented it — so this fills a promised-but-missing command.meridian uninstall [--purge] [--dry-run] [--yes]:~/Library/LaunchAgents/com.meridiona.*.plist(glob → nothing orphaned, any installer).~/.local/binCLI entries (DMG symlink or npm node-wrapper).~/.meridian/.env,meridian.db,oauth/,settings.json,runtime/);--purgeremoves all of~/.meridian.--yes);--dry-runchanges nothing.New
src/uninstall.rs(registered inlib.rs, dispatched inmain.rsbydoctor). Small intentional label/path duplication withbackend_install.rs— the daemon crate can't depend on the tray.Tests: label matching, agent enumeration from a temp dir, missing-dir = empty. Verified live with
--dry-run(correctly enumerated all 3com.meridiona.*agents + the staged a11y binary + marker +~/.local/binentries, preserved user data). Never ran the real delete on the dev machine.Follow-ups (not in this slice)
meridian doctorcheck that flags orphaned agents when the.appis gone (the advisor's "+ doctor check").Ticket changed by: Akarsh-Hegde