Snippets Service

8.32 SnippetsService — Contribute :shortcode: expansions to the global keystroke matcher

Runs in: worker only.

Permission required: snippets:contribute.

SnippetsService lets an extension contribute a static dictionary of :shortcode: → expansion pairs to Asyar's system-wide snippets engine. Once registered, typing one of your shortcodes in any text input on any application — browser address bar, code editor, chat client — triggers an in-place replacement, just as if the user had created a snippet manually. No window opens, no UI flickers.

Keys follow the Slack-style bounded form :[a-z0-9_+-]{1,32}:. The opening : arms the matcher; the closing : commits the replacement. Examples: :party:, :red_heart:, :+1:, :a-b:. Uppercase, spaces, and out-of-charset characters are rejected at registration time.

/** A dictionary mapping bounded shortcodes (`:xxx:`) to their expansion strings. */
export type ShortcodeMap = Record<string, string>;

export interface ISnippetsService {
  /**
   * Contribute a static dictionary to the launcher's global keystroke matcher.
   * Calling again replaces the calling extension's previous contribution
   * wholesale. Malformed keys cause the proxy to reject the whole call —
   * partial registration is never allowed.
   */
  registerShortcodes(map: ShortcodeMap): Promise<void>;

  /** Remove the calling extension's entire contribution. Idempotent. */
  unregisterShortcodes(): Promise<void>;
}

Usage:

import type { ISnippetsService, ShortcodeMap } from 'asyar-sdk/contracts';

// In your worker entry point:
const snippets = context.getService<ISnippetsService>('snippets');

const map: ShortcodeMap = {
  ':party:': '🎉',
  ':fire:': '🔥',
  ':red_heart:': '❤️',
};

await snippets.registerShortcodes(map);

// Remove the contribution (e.g. when the extension is being deactivated):
await snippets.unregisterShortcodes();

How it works under the hood:

The SDK proxy (SnippetsServiceProxy) validates every key against the SHORTCODE_PATTERN regex (^:[a-z0-9_+-]{1,32}:$) before dispatch. On success it sends snippets:registerShortcodes to the launcher via the IPC broker. The launcher's ExtensionIpcRouter validates the snippets:contribute permission, then forwards the payload directly to the Rust contribute_shortcodes Tauri command (the snippets namespace bypasses the JS service registry and routes Tauri-direct).

On the Rust side, contributions are namespaced by extension id and stored in a separate contributed_snippets map on AppState. The keystroke matcher merges the user's manually-created snippets (the existing snippets engine catalog) on top of every extension's contribution at lookup time. User snippets always shadow extension contributions on key collision — if the user has :party: mapped to PARTY!, your :party: → 🎉 contribution is silently masked for that user.

Uninstalling your extension drops its entire contribution atomically; you do not need to call unregisterShortcodes from a teardown hook.

Replace-style semantics:

registerShortcodes is replace-style, not additive. Each call replaces your extension's entire previous contribution. To grow the catalog, send the full updated map — there is no partial-update method.

await snippets.registerShortcodes({ ':party:': '🎉' });
await snippets.registerShortcodes({ ':fire:': '🔥' }); // :party: is now gone

Inline AI fallback (launcher-side, not part of this contract):

When the user types :xxx: and no entry in the merged catalog (user snippets + every extension's contribution) matches, the launcher emits a shortcode-miss event. A built-in silent agent can be wired to that event to resolve the unknown shortcode via AI and paste-replace it inline. The cache + rate-limit + promote-to-snippet flow is a launcher feature gated by its own settings — extensions don't dispatch the AI agent themselves and don't need to be aware of it.

Platform considerations:

System-wide expansion works on macOS, Windows, and X11/XWayland Linux. Pure Wayland sessions cannot deliver global keystrokes to unprivileged processes (Wayland security model); on those sessions the listener becomes a one-time-warned no-op. The picker UI inside Asyar itself still works. Matches Espanso / AutoKey posture.

On macOS, system-wide expansion additionally requires Accessibility permission (System Settings → Privacy & Security → Accessibility). The user grants this once when first enabling snippets; your extension does not request it.

The launcher resolves typed characters through OS-native APIs (NSEvent on macOS, ToUnicodeEx on Windows, libxkbcommon on Linux) so shortcodes work correctly on every keyboard layout the OS knows about — AZERTY, QWERTZ, Dvorak, IME composition, dead keys, etc. — without per-extension configuration.

Placement guidance:

SnippetsService is exposed only in the worker proxy bag. Attempting to call it from the view bundle fails at module load with a role-assertion error. Register your contribution from activate() in the worker and let the launcher handle the lifetime — there is no DOM dependency.

Shortcode keys must match ^:[a-z0-9_+-]{1,32}:$. The proxy rejects the whole call (atomic) if any key is malformed. Use short, lowercase, snake-case identifiers. Pick a unique prefix if your extension contributes many entries — there's no formal namespacing, just convention — to keep your contributions visually distinct from other extensions in the user's typing flow.