{"id":48963693,"url":"https://github.com/moshe-ship/lisan","last_synced_at":"2026-04-18T03:02:26.248Z","repository":{"id":352105301,"uuid":"1213785586","full_name":"Moshe-ship/Lisan","owner":"Moshe-ship","description":"Arabic-first local dictation for macOS. Whisper.cpp + Metal GPU. Your voice never leaves your Mac.","archived":false,"fork":false,"pushed_at":"2026-04-17T22:27:34.000Z","size":2008,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-17T23:31:18.477Z","etag":null,"topics":["arabic","dictation","local-first","macos","privacy","rtl","speech-to-text","swift","swiftui","voice","whisper"],"latest_commit_sha":null,"homepage":"https://moshe-ship.github.io/lisan-site/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Moshe-ship.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-17T19:00:25.000Z","updated_at":"2026-04-17T22:27:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Moshe-ship/Lisan","commit_stats":null,"previous_names":["moshe-ship/lisan"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Moshe-ship/Lisan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moshe-ship%2FLisan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moshe-ship%2FLisan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moshe-ship%2FLisan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moshe-ship%2FLisan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Moshe-ship","download_url":"https://codeload.github.com/Moshe-ship/Lisan/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moshe-ship%2FLisan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31954736,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arabic","dictation","local-first","macos","privacy","rtl","speech-to-text","swift","swiftui","voice","whisper"],"created_at":"2026-04-18T03:02:21.009Z","updated_at":"2026-04-18T03:02:26.237Z","avatar_url":"https://github.com/Moshe-ship.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lisan — Arabic-First Local Dictation for macOS\n\nForked from [Ankit-Cherian/steno](https://github.com/Ankit-Cherian/steno) (MIT).\n\n## What changed\n\nThis fork adds explicit Arabic and bilingual Arabic/English support to Steno's local-first\ndictation architecture. The core insertion, recording, and session-coordination logic is\nunchanged — only the transcription layer was extended to handle language selection and\nvocabulary biasing.\n\n## Architecture\n\n```\nAudioCapture (MacAudioCaptureService)\n    │\n    ▼\nSessionCoordinator\n    │  coordinates the full dictation pipeline\n    │\n    ├──► TranscriptionEngine (WhisperCLITranscriptionEngine)\n    │        │\n    │        │  Loads vocabulary file at init; passes --prompt to whisper-cli.\n    │        │  Maps LanguageMode to whisper -l arg: en / ar / (auto = omit arg).\n    │        ▼\n    │    RawTranscript\n    │\n    ├──► CleanupEngine (BilingualCleanupEngine → RuleBasedCleanupEngine)\n    │    │\n    │    │  BilingualSentenceSplitter tags each chunk as arabic/english/mixed/other.\n    │    │  Arabic chunks → ArabicNormalizer → ArabicPunctuator.\n    │    │  English / mixed chunks → base RuleBasedCleanupEngine (English rules).\n    │    │  Fast path: no Arabic detected → skip split, base engine handles whole input.\n    │    ▼\n    │  CleanTranscript\n    │\n    └──► InsertionService\n             │\n             ├──► DirectTypingInsertionTransport  (CGEvent Unicode key events)\n             ├──► AccessibilityInsertionTransport (AXValueAttribute patching)\n             └──► ClipboardInsertionTransport     (NSPasteboard + Cmd+V for terminals)\n```\n\n### Key files (unchanged from upstream)\n\n| File | Role |\n|------|------|\n| `StenoKit/Sources/StenoKit/Services/MacInsertionTransports.swift` | CGEvent typing, AX patching, terminal paste |\n| `StenoKit/Sources/StenoKit/Services/InsertionService.swift` | Transport prioritization, terminal clipboard-first |\n| `StenoKit/Sources/StenoKit/Services/SessionCoordinator.swift` | Pipeline orchestration, actor-isolated |\n| `StenoKit/Sources/StenoKit/Services/MacAudioCaptureService.swift` | AVAudioEngine recording → .m4a file |\n| `StenoKit/Sources/StenoKit/Services/AppContextProvider.swift` | Frontmost app detection (bundle ID, isIDE) |\n| `StenoKit/Sources/StenoKit/Services/PersonalLexiconService.swift` | Whole-word lexicon corrections (regex \\b boundary) |\n\n### Changed files\n\n| File | Change |\n|------|--------|\n| `StenoKit/Sources/StenoKit/Models/Profiles.swift` | Added `LanguageMode` enum: `.en`, `.ar`, `.auto` |\n| `StenoKit/Sources/StenoKit/Services/WhisperCLITranscriptionEngine.swift` | Added vocabulary-file loading; updated `normalizeLanguage` for Arabic; `LanguageMode`-aware hint → `-l` arg |\n| `StenoKit/Sources/StenoKit/Services/SessionCoordinator.swift` | `stopPressToTalk` now takes `LanguageMode` instead of raw `languageHints` |\n| `Steno/AppPreferences.swift` | Added `languageMode: LanguageMode` and `vocabularyFilePath: String` to `Dictation` |\n| `Steno/DictationController.swift` | Wires `vocabularyFilePath` into `WhisperCLITranscriptionEngine` init |\n| `Steno/LanguageSettingsSection.swift` | New: segmented picker (en / ar / auto) + vocabulary file path field |\n| `Steno/SettingsView.swift` | Wires `LanguageSettingsSection` |\n| `Steno/StenoApp.swift` | Window title changed from \"Steno\" to \"Lisan\" |\n| `StenoKit/Package.swift` | `swift-tools-version: 6.1` (was 6.2; 6.1.2 is the installed toolchain) |\n\n## Language mode\n\n```\npreferences.dictation.languageMode\n  .en    → whisper-cli -l en        (explicit English)\n  .ar    → whisper-cli -l ar        (explicit Arabic)\n  .auto  → whisper-cli (no -l flag) (whisper auto-detects)\n```\n\n`normalizeLanguage()` also handles: `en-US`, `ar-EG`, `arabic`, `english` as aliases.\n\n## Arabic cleanup layer\n\nLisan ships four composable services that turn Whisper's raw Arabic output into\nclean written prose, routed per-sentence so mixed-language transcripts aren't\ncorrupted by English-only rules:\n\n| Service                        | Role                                                                 |\n|--------------------------------|----------------------------------------------------------------------|\n| `BilingualSentenceSplitter`    | Tags sentence chunks as `.arabic` / `.english` / `.mixed` / `.other` |\n| `ArabicNormalizer`             | User-toggleable transforms (harakat, tatweel, alef, ya, digits...)   |\n| `ArabicPunctuator`             | Converts `,` → `،`, `;` → `؛`, `?` → `؟` inside Arabic chunks only   |\n| `BilingualCleanupEngine`       | Wraps a base engine, routes per-chunk, joins results                 |\n\nAll four are pure, `Sendable`, unit-tested (179/179 suite), zero-cost fast-path\nwhen the transcript contains no Arabic.\n\n### Normalization toggles\n\nDefault-on (always-safe):\n- Strip harakat (ً ٌ ٍ َ ُ ِ ّ ْ)\n- Strip tatweel (ـ)\n- Unify hamza-on-alef (أ إ آ ٱ → ا)\n\nDefault-off (change meaning — opt in per dialect):\n- Unify ya (ى ئ → ي)\n- Fold teh marbuta (ة → ه)\n- Fold waw-with-hamza (ؤ → و)\n- Digits to ASCII (٠-٩ → 0-9) or to Arabic-Indic (0-9 → ٠-٩)\n\nLive preview in Settings → Arabic shows every transform's effect on a sample.\n\n## Vocabulary file or directory\n\nA plain-text file, one phrase per line, loaded once at engine init. Lines\nstarting with `#` are comments, blank lines ignored, phrases deduped.\n\n**Point Lisan at a directory and it reads every `.txt` file inside**, sorted\nalphabetically — layer multiple vocabulary packs (MSA business, Khaleeji,\nShami, Saudi places, GCC brands, agency-bilingual) without merging them by\nhand. See [`packs/README.md`](packs/README.md) for included packs.\n\nThe concatenated phrases are joined with spaces and passed to whisper-cli as\n`--prompt \"phrase1 phrase2 ...\"` to bias recognition.\n\n```\n# ~/.lisan/vocabulary.txt\nماجدة\nأبوaja\nPerformance MAX\nNashir\nhurmoz\nOpenClaw\n```\n\nThe engine uses the prompt to bias recognition toward custom terms, brand names,\nArabic proper nouns, and transliterations that the base model may not handle well.\n\n## Insertion behavior\n\nUnchanged from Steno. Three-tier fallback:\n\n1. **Direct typing** — CGEvent Unicode keyboard events (chunked, 20 code units, 10ms delay).\n   Best-effort verification via AXValueAttribute comparison before/after.\n2. **Accessibility API** — AXUIElementSetAttributeValue on the focused text element.\n   Handles selections (replaces selection range), restores caret position.\n3. **Clipboard paste** — NSPasteboard write + Cmd+V synthesis.\n   Terminal apps (Terminal, iTerm2, Warp, Codex) skip directly to this tier.\n\nTerminal safety is achieved by putting clipboard first for:\n`dev.warp.warp-stable`, `com.openai.codex`, `com.apple.terminal`, `com.googlecode.iterm2`.\n\n## Build status\n\n### whisper.cpp + Metal (pre-built and verified)\n\n```\nRepository:  ~/vendor/whisper.cpp/\nBinary:      ~/vendor/whisper.cpp/build/bin/whisper-cli\nBase model:  ~/vendor/whisper.cpp/models/ggml-base.bin   (141 MB, 99 languages)\nSmall model: ~/vendor/whisper.cpp/models/ggml-small.bin  (better accuracy, optional)\n```\n\nVerified with real audio (JFK sample, 3.3 min → 419ms decode on M4 Max Metal):\n```\n$ whisper-cli -m models/ggml-base.bin -f samples/jfk.wav -t 8 --no-timestamps -l en\nAnd so my fellow Americans: ask not what your country can do for you,\nask what you can do for your country.\n```\n\n### StenoKit (core engine)\n\n```\nswift build  → Build complete! (0 errors, 0 warnings)\nswift test   → 179/179 tests passed (74 new tests across Arabic layer + vocab loader)\n```\n\nNo Xcode required. `swift build` compiles all engine, service, and model code.\n\n### Lisan.app (full app)\n\n```\nxcodegen generate  → Steno.xcodeproj created\nxcodebuild         → BLOCKED: no full Xcode installed on this machine\n                       only Command Line Tools are present\n```\n\nThe app requires a full Xcode installation to build. The Steno.xcodeproj is generated\nand ready; run `xcodebuild` on a machine with Xcode.\n\n## Blockers\n\n1. **Full Xcode required** — The host (arm64 macOS 26.4.1, Swift 6.1.2, CLT-only)\n   cannot run `xcodebuild`. Install Xcode from the Mac App Store to build the `.app` bundle.\n   StenoKit itself builds and tests fully with `swift build`.\n\n2. **No microphone in headless environment** — Real utterance tests (English, Arabic, mixed)\n   cannot be run on this machine. Must be tested on a MacBook with a microphone.\n\n3. **Accessibility permission** — Direct typing and AX insertion require\n   System Settings → Privacy \u0026 Security → Accessibility → Lisan (toggle on).\n\n4. **Microphone permission** — Required for recording. Prompt shown on first launch.\n\n## Release verification\n\nEvery release asset is Developer-ID signed, Apple-notarized, ticket-stapled,\nand has its entitlements reduced to `audio-input` only. See\n[RELEASE_VERIFICATION.md](RELEASE_VERIFICATION.md) for the six-command audit\ncycle you can run against any published zip. `scripts/package-release.sh`\nruns the same checks as a self-test before emitting the final zip — so\nthe manual audit and the build use the same definition of green.\n\n## Transcript examples\n\n### Verified (real whisper-cli, JFK audio sample)\n\n| Audio | Mode | Output |\n|-------|------|--------|\n| JFK 1961 inaugural address (3.3 min) | `.en` | \"And so my fellow Americans: ask not what your country can do for you, ask what you can do for your country.\" |\n| JFK 1961 (auto-detect) | `.auto` | Same — auto-detected English correctly |\n\n### Pending (await microphone access)\n\n| Utterance | Mode | Expected |\n|-----------|------|----------|\n| \"The quarterly report is ready\" | `.en` | The quarterly report is ready |\n| \"التقرير الربعي جاهز\" | `.ar` | التقرير الربعي جاهز |\n| \"I need the تقرير مالي\" | `.auto` | I need the تقرير مالي |\n| \"Performance MAX campaign\" (vocab loaded) | `.en` | Performance MAX campaign |\n\n## Next steps\n\n1. Install full Xcode → build `.app` bundle\n2. Install whisper-cli + multilingual model → test actual transcription\n3. Verify Arabic insertion renders correctly in target apps (RTL text handling)\n4. Test mixed Arabic/English code-switching in auto mode\n5. Add vocabulary file test: confirm measurable improvement for known proper nouns\n6. Consider adding Arabic diacritics normalization layer (optional, phase 2)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoshe-ship%2Flisan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoshe-ship%2Flisan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoshe-ship%2Flisan/lists"}