Skip to content

Read Raycast 2.x (RAYCFG3) backups - #656

Open
tiliakoos wants to merge 1 commit into
SuperCmdLabs:mainfrom
tiliakoos:fix/raycast-2x-backup-import
Open

tiliakoos wants to merge 1 commit into
SuperCmdLabs:mainfrom
tiliakoos:fix/raycast-2x-backup-import

Conversation

@tiliakoos

Copy link
Copy Markdown

What changed

Adds support for the .rayconfig format Raycast 2.x writes. src/main/raycast-v2-backup.ts decrypts the new container and maps the payload back onto the 1.x shape, so every existing importer, preview and count keeps working untouched.

Why

A Raycast 2.x export fails today, and the failure is reported as Password was not valid no matter what the user types. Raycast changed both halves of the export:

Container. 2.x writes RAYCFG3\n + UInt32LE header length + gzipped header JSON + AES-256-GCM ciphertext + a 16-byte auth tag. The key is scrypt over the password and the header's salt (N=16384, r=8, p=1, dkLen=32). decryptRaycastBuffer feeds the whole file to OpenSSL as salt-less AES-256-CBC, which cannot decrypt it, and the resulting failure falls into the wrong-password branch.

Payload. The decrypted JSON is category-keyed (settings, quicklinks, notes, nodeExtensions, ai, ...) rather than the 1.x builtin_package_* keys. Even a successful decrypt would find nothing to import.

Closes #652.

How it works

Detection is by container signature, checked before the plain-JSON probe. A RAYCFG3 file takes the new path; 1.x encrypted backups and unencrypted JSON exports take exactly the path they took before.

Mapped: global hotkey, window mode, pop-to-root timeout, navigation bindings, quicklinks, notes, snippets, store extensions with their per-command enabled state and preference values, command hotkeys, command aliases, and AI chats. Clipboard history and MCP servers are carried as counts only, so the preview keeps listing them as unsupported.

Three details worth review:

  • Command ids. 2.x ids are opaque (c:n:<extension-uuid>::-::<command>). The converter emits root-search records only for the kinds SuperCmd can resolve: store extension commands by extension name, applications by path, and built-ins through an explicit id map. Quicklink, Apple Shortcut and AI-tool entries are skipped rather than approximated, since SuperCmd mints its own ids for those and a near-miss would bind a hotkey to the wrong command.
  • Aliases. 2.x records an explicit alias field, so it is used verbatim (trimmed and lowercased). The existing heuristic that guesses an alias out of 1.x searchTerms discards anything under three characters, which would throw away real two-character aliases. The heuristic still runs for 1.x backups.
  • Secrets. settings.nodeExtensions[].oauthTokens and settings.internalExtensions[].meta (which holds BYOK API keys) are never read. A test asserts nothing from those fields reaches the converted backup.

Compatibility impact

None for existing backups. The 1.x decrypt path, the plain-JSON path, and every downstream importer are unchanged. The only edits outside the new module are additive: an optional alias field on the root-search record type, an alias resolver that prefers an explicit alias over the guess, emacs accepted as a navigation-bindings value, and the 2.x built-in id map merged into the existing one.

How it was tested

  • scripts/test-raycast-v2-import.mjs — 11 cases over a synthetic RAYCFG3 fixture built with a known password: container recognition, decrypt round-trip, wrong password producing the exact string the retry prompt matches, malformed and truncated containers reporting as malformed rather than as a bad password, unsupported container version, hotkey encoding, the category mapping, extension preference joining, secret exclusion, root-search emission rules, and AI chat message rejoining with tool turns dropped. No real .rayconfig is committed; the harness builds its own.
  • npm test — 95 pass, 0 fail.
  • npm run build — clean.
  • Verified end to end against a real Raycast 2.3.0.0 export on macOS 26.6: 4 quicklinks, 4 notes, 13 extensions, 3 with preference values, 68 disabled extension commands, 33 AI chats, and 19 resolvable command records carrying 7 hotkeys and 16 aliases.

Verified after the fix by importing that same export into a local build: 4 quicklinks, 4 notes, 13 extensions, 33 AI chats, 7 command hotkeys and 16 aliases all landed.

Raycast 2.x changed both halves of the export and the importer only knew
the 1.x versions of each, so every 2.x `.rayconfig` failed and the failure
surfaced as "Password was not valid" no matter what the user typed.

The container is now `RAYCFG3\n` + UInt32LE header length + gzipped header
JSON + AES-256-GCM ciphertext + a 16-byte tag, keyed by scrypt over the
password and the header's salt. The old path fed the whole file to OpenSSL
as salt-less AES-256-CBC, which cannot decrypt it. The payload is also
category-keyed (`settings`, `quicklinks`, `notes`, ...) instead of the 1.x
`builtin_package_*` keys, so even a successful decrypt would have found
nothing to import.

`raycast-v2-backup.ts` decrypts the container and maps the payload back
onto the 1.x shape, leaving every existing importer, preview and count
untouched. Detection is by container signature, so 1.x encrypted backups
and plain JSON exports still take exactly the path they took before.

Mapped: global hotkey, window mode, pop-to-root timeout, navigation
bindings, quicklinks, notes, snippets, store extensions with their
per-command enabled state and preference values, command hotkeys and
aliases, and AI chats (whose messages 2.x stores in a sibling array and
which are rejoined by chat id). Clipboard history and MCP servers are
carried as counts only so the preview keeps reporting them unsupported.

Command ids are emitted only for the kinds SuperCmd can actually resolve:
store extension commands by extension name, applications by path, and
built-ins through an explicit id map. Quicklink, Apple Shortcut and
AI-tool entries are skipped rather than guessed at, because SuperCmd mints
its own ids for those and a near-miss would bind a hotkey to the wrong
command. OAuth tokens and BYOK API keys are never read.

Aliases are taken verbatim from 2.x's explicit `alias` field instead of
the heuristic that has to guess one out of 1.x's `searchTerms`; that
heuristic discards anything under three characters and would have dropped
real two-character aliases.

Claude-Session: https://claude.ai/code/session_01VfHuFnP7ZZ1AYqRHURZPEo
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.

Import settings from Raycast issues

1 participant