Skip to content

awsleiman-lab/gibran

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gibran

Inline AI typing suggestions, anywhere on your Mac — fully local.

Gibran is a menu-bar typing assistant. It reads the focused text field through the macOS Accessibility API, generates a low-latency continuation with a local LLM, and paints it as gray ghost text right at your caret — in Notes, Mail, Safari, Xcode, Electron apps, anything that exposes text. Press Tab to accept word by word, Ctrl+Option+Return to take it all, Escape to dismiss. Nothing you type ever leaves your Mac.

Gibran suggesting the next words as inline ghost text

Download for macOS 14+ on Apple Silicon — signed and notarized, auto-updates via Sparkle. Or build it yourself below. Licensed under AGPL-3.0 (see Acknowledgements).

Why it's interesting

  • System-wide, not per-app: one assistant for every text field on the system, driven entirely through public Accessibility APIs.
  • Local inference: a llama.cpp server running Gemma (with a tiny in-process fallback) — no accounts, no API keys, no cloud.
  • The hard part is the caret: reading trustworthy caret geometry across arbitrary apps is where most of the engineering lives — see Alignment for the tiered approach.
  • Learns your style on-device: accepted/rejected suggestions feed a local SQLite style store used for few-shot priming.

Build from source

Gibran currently supports Apple Silicon Macs (M1 or newer). The release runtime is built from the exact llama.cpp commit in LLAMA_CPP_REVISION; no Homebrew runtime is required by users.

git clone https://github.com/awsleiman-lab/gibran.git
cd gibran
./script/build_and_run.sh

The run script builds dist/Gibran.app with the stable bundle identifier com.awsleiman.gibran, signs it with the first available Apple Development or Developer ID Application identity, verifies the signature, and launches the app. Override either value when needed:

GIBRAN_BUNDLE_ID="com.example.gibran" ./script/build_and_run.sh
GIBRAN_CODESIGN_IDENTITY="Apple Development: you@example.com (TEAMID)" ./script/build_and_run.sh

To remove the local bundle, clear saved settings/logs, and reset Accessibility and Input Monitoring TCC entries for both the current and legacy bundle ids:

./script/build_and_run.sh --uninstall

For manual permission testing, install and launch the signed copy from /Applications/Gibran.app:

./script/build_and_run.sh --install

Test Loop

  1. Launch Gibran from the Run action or script.
  2. Open the menu bar item and choose Request Permission.
  3. Grant Accessibility permission in System Settings.
  4. Grant Input Monitoring permission if macOS prompts while enabling Tab accept.
  5. Type in any app with a focused text field, including unknown or custom apps.
  6. If needed, use Settings App Exceptions to disable apps where suggestions should never appear.
  7. Start the local model server, or let Gibran use the tiny fallback while the server is offline.
  8. Try phrases such as thank, please let me know, i look forward, or the next.
  9. Accept the overlay suggestion with Tab.
  10. If Tab accept is unavailable or under validation, accept with the fallback debug shortcut: Control Option Return.

How the suggestion is placed depends on how much Gibran can trust the caret geometry it reads (see Alignment below). When the focused app exposes a real caret rect, the suggestion appears as gray ghost text inline beside the caret. Inline ghosts must also fit inside both the focused editor and its app window; otherwise Gibran uses a popup card clamped inside that window. When the caret can only be estimated (Electron/web apps such as Codex), or the caret sits mid-line where ghost text would paint over existing characters, the suggestion instead appears as a small popup card anchored just below the line. If no usable caret can be resolved at all, Gibran keeps the suggestion active for Tab acceptance but shows no overlay rather than placing a wrong one.

Alignment

Reading the caret position across arbitrary macOS apps is the hard part. Gibran uses a tiered approach — the problem space was mapped out by earlier projects in this niche (see Acknowledgements); Gibran's implementation is its own:

  • Caret-rect confidence (CaretRectConfidence): every caret is tagged precise (real AXBoundsForRange), coarse (a usable but rough selection rect), frameOnly (only the element frame is known), or synthesized (computed by the TextKit estimator below).
  • Per-display coordinate conversion (DisplayCoordinateBridge): AX rects (top-left origin) are flipped into AppKit space inside the display that owns the rect, so placement stays correct on mixed-DPI and stacked monitors.
  • TextKit caret estimator (TextLayoutCaretEstimator): when no real caret is exposed, Gibran lays the text-before-caret out in a detached TextKit stack and reads where the caret falls, rejecting the estimate when it cannot be trusted (scrolled content, tab stops, truncated context).
  • Render-mode policy (GhostPlacementRule): trustworthy + end-of-line carets render inline ghost text; everything else renders the popup card.
  • Chromium/Electron wake (WebContentAXActivator): sets AXManualAccessibility on non-Apple apps so Chromium/Electron surfaces expose their web text.
  • Stability gate (OverlayRepositionDamper): holds overlay position against sub-pixel AX noise so it does not shimmer in place.

Terminal emulators (and other apps that render glyphs directly without exposing editable text through Accessibility) remain unsupported by design.

Tab accept uses a keyboard event tap. macOS may require Input Monitoring permission before the event tap can observe and conditionally consume Tab. Gibran reports the Tab accept monitor status in the debug window, menu bar, and settings window. If macOS disables the event tap because of a timeout or user input, Gibran re-enables it and records the disable/re-enable event in the debug UI and JSONL event log.

V2 Settings Slice

Gibran now persists the Settings window's Assistant Enabled preference locally and applies it on launch. The assistant still defaults to enabled.

Settings also includes Suggestion Delay, local model controls, and App Exceptions toggles for common apps. Gibran allows apps by default and persists only disabled bundle identifiers locally.

See ROADMAP.md for the current version status and planned V5-V6 work.

Manual Validation Checklist

Validate a mix of built-in, third-party, and custom apps:

  • TextEdit
  • Notes
  • Safari
  • Mail
  • Xcode
  • A custom app with a standard text field

For each app, record:

  • Whether nearby context was captured in the debug window.
  • Whether Settings reports the expected context source, fragment count, confidence, and freshness without displaying captured words.
  • Whether the per-app window-context switch blocks all surrounding context and the separate screen-reading switch blocks only screenshot/OCR fallback.
  • Whether the Focus Diagnostic explains ready, disabled, non-text, secure, or unreadable focus states.
  • Whether a suggestion appeared.
  • Whether overlay placement used caret bounds or stayed hidden because caret geometry was unavailable.
  • Whether Tab inserted the suggestion only while a suggestion was active.
  • Whether Tab passed through when no suggestion was active.
  • Whether Tab passed through when the assistant was paused.
  • Whether Tab passed through after focus moved or the focused context changed.
  • Whether the debug UI and event log reported Tab event-tap availability or any disable/re-enable events seen during testing.
  • Whether Control Option Return still inserted the suggestion as the fallback/debug accept shortcut.
  • Whether insertion failed or changed focus unexpectedly.
  • Suggestion latency shown in the debug window or event log.
  • Disable the app in Settings and confirm suggestions stop and Tab passes through with a disabled-app diagnostic.

Local Model

Gibran defaults to Gemma 4 E4B Base through llama.cpp's local OpenAI-compatible server. Base weights are intentionally used for inline continuation: they continue the draft instead of treating it as a conversation turn that needs a reply.

./script/install_llama.sh
./script/start_model_server.sh

The default model reference is mradermacher/gemma-4-E4B-GGUF:Q4_K_M, served at http://127.0.0.1:42171/v1/completions. The start script downloads the GGUF through llama.cpp on first run (≈5 GB) and keeps the model warm for completion calls.

The managed picker has three deliberately narrow choices: Fast — Qwen3 4B Base, Recommended — Gemma 4 E4B Base, and Best Quality — Gemma 4 12B Base. The in-process TinyLocalCompletionModel still handles offline fallback when the local server is unavailable.

Managed models use raw text-completion prompts. User-supplied services continue to use the OpenAI-compatible chat-completions contract for backward compatibility.

You can override the model or server parameters:

GIBRAN_MODEL="mradermacher/Qwen3-4B-Base-GGUF:Q4_K_M" ./script/start_model_server.sh
GIBRAN_LLM_PORT=8081 ./script/start_model_server.sh

The Settings window also exposes model display name, model reference, endpoint, timeout, and server status so V3 model experiments do not require code edits.

For local development, keep ./script/start_model_server.sh as the supported server path. A user LaunchAgent can be used as a convenience to keep the model warm between launches, but it is optional local setup.

To run the repeatable TextEdit smoke benchmark after installing Gibran:

./script/benchmark_textedit.sh

The benchmark drives TextEdit through the same Accessibility/Input Monitoring path and summarizes metadata from the local JSONL log. It does not log nearby typed text or suggestion text, so bad-output and no-suggestion rates are manual visual counts.

Current Prototype Boundaries

  • Completion tries Gemma 4 E4B Base first through a local llama.cpp server. If that server is unavailable or returns nothing, TinyLocalCompletionModel remains as an in-process fallback.
  • Local model output is cleaned before display so prompt delimiters, prompt labels, app names, copied stock phrases, and literal no-answer responses do not appear as suggestions.
  • Logs record lifecycle, permission, app, failure, acceptance, and latency metadata only. Nearby text is displayed in the debug window but is not written to the JSONL log.
  • Secure text fields, disabled apps, and non-text focused elements are skipped with a visible Focus Diagnostic in Settings, the menu bar, and the debug window.
  • Insertion first uses Accessibility selected-text replacement, then falls back to setting the field value when the focused app allows it.
  • The suggestion surface is a borderless panel that renders either inline gray ghost text (trustworthy caret, end of line) or a popup card (estimated caret or mid-line), per the Alignment section above. Gibran samples attributed text style when available, then falls back to a local default. Inline text is not drawn inside the target app's own text layout, because arbitrary macOS apps do not expose a generic inline rendering API through Accessibility.
  • Plain Tab is conditionally consumed only when the assistant is running, an active suggestion exists, the focused text role is supported, the cursor range is collapsed, and the latest focused context still matches the suggestion context. Control Option Return remains the fallback/debug accept shortcut.
  • The context match includes caret bounds when Accessibility exposes them. When caret bounds are unavailable, two fields with identical text and selection in the same app can still collide; in that case Tab safety falls back to the remaining app, role, selection, and text checks.

Acknowledgements

Gibran exists because others explored this niche first. Cotypist proved that a system-wide, local-model typing assistant on macOS is a product worth wanting — Gibran began as an attempt to build that experience for myself. Cotabby (AGPL-3.0) mapped much of the caret-geometry and overlay problem space in the open; studying it shaped Gibran's early overlay architecture, and Gibran is licensed AGPL-3.0 accordingly.

About

Inline AI typing suggestions anywhere on macOS — fully local, powered by a llama.cpp model. AGPL-3.0.

Topics

Resources

License

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors