10 KiB
Phase 10C: ASAR Runtime Packaging
Goal
Move the desktop package from fully unpacked local-validation output to a more release-like ASAR package while keeping Claude's externally executed MCP and hook commands available as regular unpacked files.
Phase 10C should reduce package over-inclusion from Phase 10A/10B and prove the packaged app still works with bundled Claude commands.
Non-goals
- No public signed/notarized release.
- No installer targets beyond the current
--dirpackage checkpoint unless trivial. - No npm publishing.
- No auto-update.
- No start-at-login.
- No new agent integrations.
- No changing Claude hook/MCP behavior except paths needed for ASAR packaging.
User-visible/manual outcome
The current-platform package still builds with:
pnpm package:desktop:dir
The generated app uses ASAR for the Electron app contents, but still includes unpacked files that Claude can execute with external node:
app.asar
app.asar.unpacked/node_modules/@open-pets/mcp/dist/index.js
app.asar.unpacked/node_modules/@open-pets/claude/dist/cli.js
Agent Setup packaged previews should point at app.asar.unpacked/... paths, not app.asar/... paths and not source checkout paths.
Acceptance criteria
electron-builderASAR packaging is enabled.- Externally executed bundled command files are unpacked:
@open-pets/mcp/dist/index.js,@open-pets/claude/dist/cli.js,- package metadata (
package.json) needed for ESM/package resolution, - all runtime files and transitive dependencies those commands need when launched by external
node.
- Packaged Agent Setup bundled MCP preview points at a regular file outside
app.asar, preferably underapp.asar.unpacked. - Packaged hook preview points at a regular file outside
app.asar, preferably underapp.asar.unpacked. - Desktop runtime imports from ASAR still work for the Electron app itself.
- Package contract checks assert:
app.asarexists,- externally executed command entry files exist under unpacked resources,
- unpacked command package metadata exists,
- external
nodecan execute packaged MCP/Claude command entry points in safe smoke modes, - generated bundled command preview paths do not contain
.asarexcept.asar.unpacked, - no symlinks escape package output,
- forbidden repo/user-data patterns remain absent.
- Runtime bundled path resolution handles both dev/unpacked and packaged ASAR layouts.
- Claude bundled path validation still rejects missing files, symlinks, and true
app.asarpaths, while allowingapp.asar.unpackedpaths. docs/mvp-validation.mdis updated to describe ASAR + unpacked command resources.pnpm testpasses.pnpm checkpasses.pnpm package:desktop:dirpasses.
Proposed files/directories
apps/desktop/electron-builder.yml- Enable ASAR and configure unpacking for externally executed OpenPets command resources.
packages/claude/src/claude-code.ts- Resolve bundled MCP path to ASAR-unpacked location when running from ASAR.
packages/claude/src/hook-settings.ts- Resolve bundled Claude CLI path to ASAR-unpacked location when running from ASAR.
packages/claude/src/check-claude-code.ts- Cover ASAR-to-unpacked path mapping.
packages/claude/src/check-claude-hooks.ts- Cover ASAR-to-unpacked hook CLI path mapping.
apps/desktop/src/check-packaging-contract.ts- Validate ASAR/unpacked package output and bundled command files.
docs/mvp-validation.md- Update packaging notes.
docs/phases/phase-10c-asar-runtime-packaging.md
Technical approach
Builder config
Enable ASAR:
asar: true
Use asarUnpack for externally executed command resources and their module metadata/dependency closure. Initial candidate:
asarUnpack:
- node_modules/@open-pets/mcp/**
- node_modules/@open-pets/claude/**
- node_modules/@open-pets/client/**
- node_modules/@modelcontextprotocol/**
- node_modules/zod/**
If runtime smoke testing shows additional MCP SDK transitive dependencies are required for external node, include them. Prefer a minimal external command runtime subtree, but correctness is more important than micro-optimizing package size in this phase.
Path mapping
When @open-pets/claude runs inside Electron from app.asar, its import.meta.url may include app.asar. Bundled command paths for Claude must map to app.asar.unpacked.
Add a pure, tested path mapper with behavior:
.../Resources/app.asar/node_modules/@open-pets/claude/dist/index.js
=>
.../Resources/app.asar.unpacked/node_modules/@open-pets/claude/dist/cli.js
.../Resources/app.asar.unpacked/node_modules/@open-pets/mcp/dist/index.js
In dev/non-ASAR mode, continue resolving sibling workspace package paths as today.
Validation must reject true app.asar command paths but allow app.asar.unpacked paths.
Validation
Package output contract should locate either:
- ASAR +
app.asar.unpackedresources from Phase 10C.
Phase 10C should prefer ASAR and fail if app.asar is missing after package output.
Because forbidden-file checks cannot simply walk inside app.asar as a directory, use an ASAR library/tool or verify through builder file config plus unpacked tree checks.
Post-package smoke checks should execute:
node <app.asar.unpacked>/node_modules/@open-pets/mcp/dist/index.js --version
node <app.asar.unpacked>/node_modules/@open-pets/claude/dist/cli.js hook --openpets-managed
The hook smoke must use isolated/missing discovery input so it does not contact a real desktop app.
Risks and tradeoffs
- Dependency completeness: external
noderunning unpacked MCP/hook files may need dependencies that were left insideapp.asar. Package contract and manual Claude tests must catch this. - Path mapping: ASAR path rewrites are easy to get subtly wrong on Windows. Keep mapping string-based and covered by tests.
- Package size: unpacking command packages duplicates some files. Accept for this MVP unless it becomes excessive.
- Future installers/signing: signing/notarization remains deferred.
Security/privacy notes
- Do not weaken renderer sandbox/context isolation/CSP.
- Do not include user data, Claude settings, backups, or secrets in package output.
- Claude config changes remain explicit/backup-protected.
- Externally executed command files must be shipped by OpenPets, not downloaded dynamically.
Test/check plan
Run:
pnpm test
pnpm check
pnpm package:desktop:dir
Automated coverage:
- ASAR path maps to ASAR-unpacked command path.
- True
app.asarcommand paths are rejected whileapp.asar.unpackedcommand paths are allowed. - Package output contains
app.asarand unpacked command resources. - Packaged command resources are regular files, not symlinks.
- External
nodesmoke-executes packaged MCP/Claude command files.
Manual verification guide
Manual verification is provided after implementation.
Oracle plan review
Reviewed by Oracle.
Blockers: none.
Should-fix feedback:
- Make
asarUnpackdependency closure explicit; externalnodecannot resolve modules left insideapp.asar. - Include package metadata in unpacked resources because ESM packages need
package.json,type, and exports metadata. - Add post-package smoke checks that actually run packaged MCP and Claude CLI entry points with safe inputs.
- Change validation semantics from rejecting any
.asarto rejecting trueapp.asarwhile allowingapp.asar.unpacked. - Add pure/tested path mapper for
app.asar→app.asar.unpacked, including Windows/backslash and spaces cases. - Update package contract for ASAR-era layout:
app.asarexists and command files are regular non-symlink files underapp.asar.unpacked. - If forbidden-file checks cannot walk ASAR contents, inspect ASAR with a tool/library or explicitly verify builder config plus unpacked tree checks.
Nice-to-have feedback:
- Prefer a minimal external command runtime subtree, but do not over-optimize before smoke checks pass.
- Document ASAR as packaging hygiene, not a security boundary.
- Keep installer/signing deferred.
Verdict: implementation-ready after tightening unpacked dependency/metadata and smoke-test requirements.
Oracle feedback disposition
Fixed:
- Added explicit unpacked dependency and package metadata requirements.
- Added external node smoke-test requirements for MCP and Claude command entry points.
- Clarified validation should reject true
app.asarbut allowapp.asar.unpacked. - Added pure ASAR path mapper requirement and ASAR-era package contract requirements.
- Clarified forbidden-file checks must account for ASAR not being a normal directory.
Accepted:
- Keep installer/signing deferred.
- Document ASAR as packaging hygiene, not a security boundary in implementation docs.
Oracle implementation review
Reviewed by Oracle after implementation.
Blocker:
- The ASAR-unpacked command target was mapped from
app.asartoapp.asar.unpacked, but the bundled validation root was still derived fromimport.meta.urlunderapp.asar. Valid unpacked command paths could be rejected as outside root.
Should-fix feedback:
- Add a test for validation/root containment behavior, not only the string mapper.
- Manual packaged Agent Setup UI verification remains required.
- Consider cleaning
dist-electronbefore packaging to avoid stale output masking regressions.
Nice-to-have feedback:
- Narrow
asarUnpacklater;node_modules/**is acceptable for MVP because command smokes prove dependency closure. - Make
mapAsarPathToUnpackedmatchapp.asaras a path segment only.
Verdict: close, but fix ASAR containment-root issue before acceptance.
Oracle implementation feedback disposition
Fixed:
- Bundled validation roots now pass through the same
app.asar→app.asar.unpackedmapper as command target paths. mapAsarPathToUnpackednow matchesapp.asaras a path segment and leavesapp.asar.unpackedunchanged.- Added mapper coverage for path-segment behavior and already-covered command smoke tests validate packaged command files under
app.asar.unpacked.
Accepted:
node_modules/**remains unpacked for this MVP phase to keep external command dependency closure reliable.
Needs manual verification:
- Packaged Agent Setup UI must be manually verified after implementation.
Final Oracle re-check:
- Blockers: none.
- Remaining should-fix: none from the prior review.
- Manual packaged Agent Setup/Claude verification remains the phase gate before closing.
- Verdict: ASAR-to-unpacked path mapping and containment validation look correct; package command smokes passed; proceed to manual packaged verification.