feat(install): one-click privileged CLI install via authenticated osascript (#4)#5
Merged
Merged
Conversation
The app's "Install CLI" button writes /usr/local/bin/engram, which is root-owned on Apple Silicon and fresh macOS, so it failed with permission denied. Add an SMAppService + XPC privileged helper that creates the symlink as root: the bundled engram CLI doubles as the daemon via a hidden _helper-daemon subcommand, registered from a LaunchDaemon plist and reached over a Mach service. The daemon takes no client-supplied paths and validates the caller's code signature before acting. On first use macOS routes the user to System Settings -> Login Items; declining falls back to sudo in Terminal. Long-term fix for #4; see ADR 0022. Runtime behaviour (daemon registration, approval, XPC code-sign check) only exercises on a signed, notarized build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The SMAppService LaunchDaemon was the wrong shape for a one-shot symlink: it registers a persistent root daemon and forces a System Settings → Login Items toggle. Replace it with a single authenticated command run through the Apple-signed /usr/bin/osascript (do shell script … with administrator privileges), which shows one native Touch ID / password dialog and leaves no daemon, login item, or helper tool behind. Because the requesting process (osascript) is Apple-signed, the auth dialog offers Touch ID when enabled. Removes the XPC protocol/daemon, the LaunchDaemon plist, the hidden _helper-daemon subcommand, and the Login Items approval UI. Rewrites ADR 0022 to record the decision (the daemon was prototyped, then rejected as overkill). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On-device testing showed `do shell script … with administrator privileges` always presents a password dialog, not Touch ID — it doesn't route through the biometric authorization path even though osascript is Apple-signed. Fix the overstated Touch ID claim in ADR 0022, README, the install sheet, and the PrivilegedInstaller doc comment; note that Touch ID would require a privileged helper, which this approach deliberately avoids. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings the terminal `engram install` into line with the app's privileged install: symlink /usr/local/bin/engram (no more copy/version-drift), and on a non-writable dir throw a clear "run sudo engram install" message instead of a raw NSError. This is also the command the app's failure fallback points users at. Ported from the short-term fix branch (claude/issue-1-discussion-33lhp5), which this folds in and supersedes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n.swift The first install commit's `git add -A` pulled a concurrent session's ADR 0023 recall-cooldown changes into main.swift, but the matching MemoryStore methods never landed on this branch — breaking the build (no member recentlyInjectedInSession). The install work's net change to main.swift is nil, so restore it to main's version. The 0023 feature stays separate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ording - Reorder EngramModel so the bundledEngramPath/runBundledEngram doc comments hug their own declarations (the new property had split them). - Add a 120s watchdog to the osascript Process so a wedged auth dialog can't hang the install sheet's spinner; report a timeout via terminationReason. - Fix stale "privileged helper" wording on InstallKind.usesPrivilegedHelper — it's the authenticated osascript path now, no helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add EngramTests for PrivilegedInstaller.shellQuoted / appleScriptEscaped — the security-relevant, pure, testable piece of the privileged install (single-quote escaping, spaces, backslash-then-quote ordering). Helpers made internal for @testable access. - Drop the locale-fragile "cancel" substring from cancellation detection; rely on osascript's -128 (userCancelledErr) alone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Long-term fix for #4: the app's Install CLI button can now write
/usr/local/bin/engramon Macs where that dir is root-owned (Apple Silicon,fresh macOS), instead of failing with permission-denied.
It performs the symlink with one authenticated prompt, by running the
operation through the Apple-signed
/usr/bin/osascript:Login Items entry, no helper tool, no XPC, no extra entitlement.
shell-quoted + AppleScript-escaped.
runnable
sudo … installterminal fallback.See ADR 0022 for the full decision, including the options survey.
Note on the history (please squash-merge)
This branch took a detour: it first prototyped an
SMAppService+ XPCprivileged daemon (
7814acb), then removed it (08ae311) once it was clear apersistent root daemon + Login Items toggle is the wrong shape for a one-shot
symlink. The
osascriptroute also does not offer Touch ID (corrected in3b2fa83after on-device testing). Squash-merge to keep that detour out ofmain's history.
Relationship to other work
engram install→ symlink + clearer error) lives onclaude/issue-1-discussion-33lhp5. This PR largely supersedes it — considerclosing that branch or reconciling the
Setup.swift/InstallSheet.swiftoverlap before/after merge.
Verification
make testgreen (97 incl. new coverage);make appbuilds & signs.osascriptflow is just a subprocess — exercised on a local signed build(one password dialog, symlink created). No notarization needed to test.
🤖 Generated with Claude Code