Skip to content

feat: Add macOS quick actions installer - #30

Merged
a1594834522-coder merged 3 commits into
memorylake-ai:mainfrom
a1594834522-coder:feat/macos-quick-actions
Sep 11, 2026
Merged

a1594834522-coder merged 3 commits into
memorylake-ai:mainfrom
a1594834522-coder:feat/macos-quick-actions

Conversation

@a1594834522-coder

Copy link
Copy Markdown
Contributor

Summary

One command installs three system-wide gestures on top of the CLI, with nothing asked of the user: ⌃⌥⌘S saves the selected text in any app as a message in a "macOS clips" conversation (the server turns it into memory), ⌃⌥⌘L searches the workspace for the selected text and shows the results in a dialog, and a Finder right-click item uploads files into a project picked from a list. The installer resolves the actor from the API key, the workspace from what the CLI is set to, and finds or creates the project and conversation by custom-id, so re-running it is idempotent and --uninstall removes everything client-side. Only tools shipped with macOS are used (zsh, osascript, PlistBuddy, shasum), so it works on a Mac that has nothing but the CLI.

curl -fsSL https://raw.githubusercontent.com/memorylake-ai/memorylake-cli/main/scripts/install-macos-quick-actions.sh | sh

Built by doing it by hand first on one machine for an afternoon; what went wrong there is baked in:

  • Without --projects the search API only looks at the actor's personal facts, never the projects, so the search action passes every project in the workspace. (search --help says the default is "every project" — it is not. Separate issue worth filing.)
  • Notifications are unreliable feedback: Focus mode hides them, banners last five seconds, and Automator's cold start adds one to three seconds of silence. People pressed twice and stored duplicates. A pop sound confirms the shortcut was received, a chime confirms success, and the message custom-id is derived from the text and the hour so a double press stores nothing twice.
  • Apps reserve shortcuts for themselves (DingTalk takes ⌃⌥⌘F; ⌘Z is undo everywhere), so the defaults are ⌃⌥⌘S / ⌃⌥⌘L and --save-key / --search-key rebind.
  • Dialogs opened from a background process land behind the frontmost window unless System Events is activated first.
  • A Finder quick action needs inputTypeIdentifier and serviceInputTypeIdentifier both set to fileSystemObject, plus a FinderActive entry in the pbs preferences, or it reports "there was a problem with the service input" or never appears in the menu.
  • defaults write cannot take pbs keys (they start with a parenthesis, which it reads as an old-style array), so the installer edits an exported copy with PlistBuddy and imports it back.

Changes

  • scripts/install-macos-quick-actions.sh: POSIX sh installer. Flags --project, --save-key, --search-key, --lang zh|en, --source, --uninstall, each with a MEMORYLAKE_QA_* variable. Writes the Automator bundles by hand, the runtime config, and the shortcut registrations; one pbs -flush at the end.
  • scripts/quick-actions/save.sh: appends the selection via conv msg append with a content-derived custom-id, retries once with --parent after a 409.
  • scripts/quick-actions/search.sh: search across every project, results in a dialog with a copy button.
  • scripts/quick-actions/upload.sh: lib upload each file, then one proj doc import into the chosen project; folders are skipped and reported.
  • scripts/quick-actions/json.js: JSON helper on osascript's JavaScript runtime (get, lines, find, plus the search and import formatters), since macOS ships neither jq nor a guaranteed python3.
  • README.md: "macOS quick actions" section under Install.
  • .github/workflows/ci.yml: the new installer joins the shellcheck -s sh step.

Test plan

  • shellcheck -s sh scripts/install-macos-quick-actions.sh clean
  • Fresh install on macOS 15 from a checkout: actor, workspace, project and conversation resolved with no prompt; three .workflow bundles and pbs entries written
  • ⌃⌥⌘S in Chrome, Notes, DingTalk and Xcode stores a message; a second press within the hour returns the same message id
  • ⌃⌥⌘L shows a dialog with facts from every project; "Copy results" fills the clipboard
  • Finder → Quick Actions → Upload on one file: uploaded, imported into the chosen project, summary notification; a folder in the selection is skipped and reported
  • --uninstall leaves no bundle, no pbs entry and no ~/.memorylake/quick-actions; reinstall restores all three
  • Language detection picks zh on a Chinese system; --lang en overrides
  • Install through curl | sh from the merged main URL (the default --source points there, so this can only be checked after merge)

🤖 Generated with Claude Code

a1594834522-coder and others added 3 commits September 11, 2026 17:52
One command installs three system-wide gestures on top of the CLI, with
nothing asked of the user:

- ⌃⌥⌘S on selected text appends it to a "macOS clips" conversation, which
  the server turns into memory in the background.
- ⌃⌥⌘L on selected text searches the workspace and shows the results in a
  dialog, with a copy button.
- Right-click a file in Finder → Quick Actions → Upload to MemoryLake
  uploads it to the Library and imports it into a project picked from a
  list.

The installer resolves the actor from the API key, the workspace from what
the CLI is set to, and finds or creates a project ("MemoryLake Quick
Actions", custom-id macos-quick-actions) and the clips conversation, so
re-running it is idempotent. It writes the Automator bundles by hand and
registers the shortcuts through the pbs preferences with PlistBuddy, since
`defaults write` cannot take keys that start with a parenthesis. Only
tools shipped with macOS are used: zsh, osascript (JavaScript for JSON),
PlistBuddy, shasum.

Lessons from doing this by hand first, baked in:

- Without --projects the search API only looks at the actor's personal
  facts, so the search action passes every project in the workspace.
- Notifications are unreliable feedback (Focus mode, five-second banners,
  Automator's cold start), which made people press twice. A pop sound
  confirms receipt, a chime success, and the message custom-id is derived
  from the text and the hour so a double press stores nothing twice.
- Apps reserve shortcuts for themselves (DingTalk takes ⌃⌥⌘F; ⌘Z is undo
  everywhere), so the defaults avoid those and --save-key/--search-key
  rebind.
- The AppleScript dialogs are opened from a background process and land
  behind the frontmost window unless System Events is activated first.
- A Finder quick action needs both inputTypeIdentifier and
  serviceInputTypeIdentifier set to fileSystemObject, and its FinderActive
  entry, or it reports "there was a problem with the service input" / never
  shows up in the menu.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The installer documents itself in its header comment; the README section
was not needed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@a1594834522-coder
a1594834522-coder merged commit 05227d0 into memorylake-ai:main Sep 11, 2026
5 of 6 checks passed
a1594834522-coder added a commit that referenced this pull request Sep 14, 2026
… TextEdit (#31)

## Summary

Two problems found on the first day of real use of the macOS quick
actions (#30). Every string in the search dialog rendered as mojibake on
a Chinese-language Mac: services run with no locale variables, zsh
therefore slices by byte, `${query:0:60}` could cut a character in half,
and osascript reacts to one invalid byte by decoding the whole script
text in the system legacy encoding (GB18030). The generated `config.sh`
now exports `LANG=en_US.UTF-8` when nothing is set. And the dialog was
the wrong control for results — `display dialog` text cannot be
selected, so copying one line meant the "copy everything" button.
Results now open as a text file in TextEdit, where any part can be
selected and ⌘W dismisses it.

## Changes

- **`scripts/install-macos-quick-actions.sh`**: the generated
`config.sh` exports a UTF-8 `LANG` when the environment has none, with a
comment on why; the two button strings are gone from the message tables.
- **`scripts/quick-actions/search.sh`**: writes the results to
`~/Library/Caches/memorylake-quick-actions/search-<timestamp>.txt`
(header: query and time) and opens it with `open -e`; files older than a
day are deleted on the next search.

## Test plan

- [x] Reproduced: `env -i` shell with `__CF_USER_TEXT_ENCODING=…:0x19:…`
and a byte-truncated Chinese string → osascript renders the whole script
as GB18030 mojibake; with `LANG=en_US.UTF-8` zsh slices by character and
the output is correct
- [x] Reinstalled from a checkout; `config.sh` carries the export
- [x] ⌃⌥⌘L from a Chinese-language account via the real service:
TextEdit opens with correct Chinese, individual lines selectable
- [x] Same run under `env -i HOME PATH
__CF_USER_TEXT_ENCODING=0x1F5:0x19:0x34` (the service's environment)
with a 40-character Chinese query
- [x] `shellcheck -s sh` clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant