GitHub Explorer
📰 公众号已发布 2026-09-30

GitHub 推荐:3 个月 7.8K stars:Abue Ammar 的 tinycast 怎么用 SwiftUI + JavaScriptCore 把 Raycast 生态偷走

GitHub: https://github.com/abue-ammar/tinycast

一句话总结

3 个月、715 commits、零三方依赖、原生 SwiftUI 渲染、跑真 Raycast 扩展、体积压到 100 MB 内的 macOS 启动器——一个孟加拉国航空公司的工程师在 3 个月里写出来的「反 Electron Raycast 替代品」。

值得关注的理由

  • 极早期大众热门:3.1 个月 7,778 stars,月均 230+ commit,github-trending 上反复出现,处于「窗口期」中段;不是昙花一现的玩具型小工具,是治理完整的商业级 codebase
  • 真正的差异化:不是「又一个 launcher」——它是唯一跑 Raycast 真实 JS bundles 并用 SwiftUI 渲染的产品(React 19 reconciler 自研 host config + JSC 嵌入 runtime)
  • 可学习密度极高:四层架构 + 机械 enforce 边界 + capability flag 安全模型 + off-by-default 设计哲学 + zero-deps 路线——大量可迁移到任何 Swift/Apple 平台项目的工程实践

项目展示

Tinycast command palette — hero: 命令面板主界面

Calculator — feature: 内置计算器扩展(跑 Raycast bundle,原生 SwiftUI 渲染)

Clipboard history — feature: 剪贴板历史(独立 tool target fork OCR,每条 ~50ms)

Per-app hotkey — feature: 应用专属快捷键

Memory usage — feature: 内存占用(<100 MB vs Raycast ~500 MB)

项目画像

维度数据
GitHubhttps://github.com/abue-ammar/tinycast
Star / Fork7,778 / 400(仅 3.1 个月)
Watcher / Open Issue8 / 多条活跃(#516 AI Actions、#533 XDG config、#663 Liquid Glass)
代码行数147,363 行(Swift 83.6% + JS 6.8% + JSON 5.6% + TSX 1.7%);源文件 825 个 Swift + 36 JS + 37 TSX
项目年龄3.1 个月(首次提交 2026-06-28)
开发阶段密集开发(近 90 天 692 commit,月均 230+;9 月逆势上扬)
开发模式业余 Side Project(周末 24.2%、深夜 63.5%;UTC+6 时区)
贡献模式单人主导(Top 作者 78.2%,加上双名 ~83%)+ 58 人外围贡献
热度定位大众热门,但仅 3 个月龄——仍属早期窗口期
质量评级代码 优秀 / 文档 优秀 / 测试 充分(84 harness + 111,684 assertions)/ CI 基本
LicenseAGPL-3.0(README 明示;GitHub auto-detect 为 Other)
Releasev0.11.11-beta.107(共 106 tag / 100 release;近 90 天 100 次 release,每两天一个 beta)

作者视角:为什么存在这个项目

创始人/作者背景

Abue Ammar(@abue-ammar),11 年 GitHub 账号,长期闲置,近 3 个月在 tinycast 单点爆发。公司是 US-Bangla Airlines(孟加拉国航空公司),地理位置 Dhaka, Bangladesh,bio 仅「die lit」。无软件行业履历,纯个人开发者——典型 indie hacker,但作品完成度达到商业团队级别。

作者对 Raycast 的反感来自具体痛点:500 MB RAM、要求账号、私有遥测、Electron 体积——这些问题在 macOS 26 + Swift 6 GA 之后,已经不再是非妥协的。这是 tinycast 时机选择的全部依据。

问题判断

  • Raycast 的「事实标准」地位是被诟病的:3000+ extensions 生态是其他 launcher 跨不过的护城河,但它的 anti-features(账号、遥测、Pro 付费)让一批开发者愿意换
  • macOS 26 是 zero-deps native launcher 的第一次可行窗口:Swift 6 strict concurrency、Observation 框架、Liquid Glass、SMAppService、Apple Silicon 普及——技术栈全部就位
  • 「latest-only, always」的窗口只有一次:错过 macOS 26 就得背上兼容性 shim 的债(standards.md 第 18-22 行明示)

解法哲学

Unix-style「做一件事做到极致」+「Migrate, never wrap」——任何兼容性 shim 是 defect;Swift 6 strict concurrency 是 hard error 不是障碍;删除版本门控代码比 feature 本身还大。

功能默认关闭(off-by-default capability flags):snippets/extensions/AI/clipboard 全部 opt-in,按需开启,权限在用到时申请。

「功能集 deliberately closed」:不是 “another launcher has it” 就加——这是反 Raycast 的另一种反法:Raycast 比 feature 数量,Tinycast 比 feature 质量。

不做什么(明文清单):不账号、不 Pro 付费、不账号绑定、不遥测、不云同步、不 iOS 同步。

战略意图

  • 核心产品,不是基础设施:个人 Side Project,但代码治理达到商业级
  • 商业化:仅 Polar.sh 一次性打赏——GitHub Sponsors 在孟加拉国不可用,走 Polar
  • 开源策略:genuinely open (AGPL-3.0),不是 open-core
  • 版本策略:长期 beta(v0.11.11-beta.107),macOS 26 主力 + macOS 15 legacy 分支

核心价值提炼

创新之处

  1. React 19 reconciler → JSON tree → native SwiftUI 渲染(4/5/3):自研 host config 把 React render commit 成 {id, type, props, children} 树;函数 props 变 {"$fn":"nodeId:propName"} handle;element-valued props 用 __slot 子节点折回 parent props(让 hooks 在里面 work)。Swift 渲染成 SwiftUI 控件
  2. 「capability flag ≠ preference」(4/5/5):snippetsEnabled 不进 settings backup——因为 backup 文件能发给别人,import 等于「无需用户操作就在别人 Mac 上装键盘监听器」
  3. GitHub tree API + recursive=1 装 extension(3/5/4):不走 contents API(cap 1000 + silent truncate + 60 calls/hour),走 trees API 单 recursive listing——3 calls vs 18+
  4. Extension artwork 0.76 vs app icon 0.83 光学校准(5/3/2):Raycast extension 是 flat fully saturated tile;macOS app icon 是 squircle with glyph inside——三条路径产出 40pt box;extension artwork 缩到 0.76 才「看起来相等」
  5. ray build -e dist -o <sibling> 而非 manifest build 脚本(4/5/3):Raycast 文档说用 manifest.build 但 dev mode 默认装到本地 Raycast(exit 0 不产出);Tinycast 显式 -e dist -o sibling/ 保证 build 出 package.json + *.js + assets/
  6. URL scheme 抢注(4/4/3):抢注 raycast://、com.raycast://、tinycast:// 三个 scheme;ExtensionHostBridge 拦截内部 URL 让 open("raycast://") 重新打开 palette 而非启 Raycast
  7. Harness 编译 ship sources(build-blocking 边界检查,3/5/5):Tests/<name>.swift 一行 run name src1 src2 ...,让 import AppKit 出现在 Model/ 就是 build error——比 linter rule 更可靠

可复用的模式与技巧

  1. Pure/Effect/Observable/View 四层分离 + 机械 enforce:grep 拒收 import AppKit|SwiftUI 在 Model/;harness 编译 ship sources 而非副本;broken build = 边界 leak 信号
  2. Off-by-default capability flags + backup 排除:每个 capability 一个 Bool 开关 + 启用 confirm dialog;capability 不进 settings backup,只进 content
  3. Dev 频道隔离 bundle ID:com.app.dev 完全独立 prefs/caches/TCC grants/login item——开发 build 不会污染用户安装的 stable 副本
  4. JavaScriptCore 嵌入 runtime(generated, committed):Resources/Runtime.generated.js 在 bundle 里直接 load,building 不需要 Node
  5. set -e + AND-OR list 陷阱:CI 跑过了一个 25 phase 没编译的 harness——run-tests.sh 把 compile 和 run 步骤分开记录失败
  6. withObservationTracking 一次性陷阱:AppCore.track 是 reference pattern——onChange 在 write 之前 fire,必须 defer re-read 进 Task + 在 Task 里 re-arm 追踪
  7. runObservingExit 替代 waitUntilExit:后者在 GCD thread 会 hang(run loop 错过 exit)
  8. Block observer 走 RAII NotificationToken:deinit 移除 observer,不靠调用方记得配对
  9. 独立 tool target 拆重型 framework:Clipboard OCR 用独立 bundle ID 嵌入 Contents/Helpers,每条 item spawn 一个 helper → 拿文本 → exit;主 app 守得住 100 MB
  10. Signposts.interval 必须 defer 包裹 work:throw 会跳过 .end emit → 显示永不关闭的 interval

关键设计决策

  1. 决策: 零三方依赖路线(zero third-party deps)—— README 强声明

    • 问题:每个 dep 都带来供应链攻击面 + 版本协调成本 + 二进制膨胀
    • 方案:任何东西要自己实现(JS runtime、React reconciler、ZIP 解压、文件 IO)
    • Trade-off:开发时间 vs 体积 & 控制力 & 信任
    • 可迁移性:低(需要产品确实能 hold 住开发时间)
  2. 决策: JavaScriptCore + 自研 React reconciler host config

    • 问题:Raycast 生态是事实标准(~3000 extensions)——不兼容就没用户
    • 方案:自研 host config 把 React 19 渲染成 JSON tree,SwiftUI 渲染;一命令一 JSContext,丢完整个 context 不复用(避免 timer + scheduler 互相干扰)
    • Trade-off:几 K 行工程债务、protocol 漏洞要持续追踪 → 换 ~0 binary size + ~7 ms warm boot
    • 可迁移性:中——但模式可推广(任何「外部 DSL + native 渲染」组合)
  3. 决策: Off-by-default capability flags

    • 问题:一个 macOS launcher 装上就监听键盘、扫文件、跑第三方代码
    • 方案:每个 capability 一个 Bool 开关 + 启用 confirm dialog;capability 不进 settings backup
    • Trade-off:用户首次体验少 features → 换「imported file 不能给你装个键盘监听器」这种安全保证
    • 可迁移性:高——任何工具类 app 都可借「capability ≠ preference」
  4. 决策: Swift 6 strict concurrency + @MainActor 默认 + 1 个 actor

    • 问题:默认 @MainActor 防止 data race;额外 actor 会增加协调复杂度
    • 方案:重 IO 显式 nonisolated static + Task.detached;cross-actor model types Sendable;@unchecked Sendable 必须有 written reason
    • Trade-off:比「多个 actor + message passing」多写一点样板 → 换「一处 reasoning location」
    • 可迁移性:现代 Swift 项目标配
  5. 决策: 「latest-only, always」—— macOS 26+ only + Swift 6 strict concurrency + 无 SwiftPM

    • 问题:兼容性 shim 累积失控(standards.md 第 18-22 行明示)
    • 方案:删除版本门控代码比 feature 本身还大 → 不做版本门控
    • Trade-off:用户基数有限(macOS 26 only)→ 换 codebase 不变胖
    • 可迁移性:中——需要产品确实没有遗留 OS 约束

竞品格局与定位

竞品对比矩阵

维度tinycastRaycastAlfredSpotlight
体积~40-80 MB / 3.6 MB binary~500 MB / ~150 MB binary~150 MB系统内置
内存(idle)<100 MB~500 MB~200 MB~50 MB
价格完全免费免费 + Pro $8/月£59 Powerpack免费
账号无必须无系统账号
遥测无私有无系统级
扩展生态跑 Raycast bundles(~3000)~3000 extensions + Storeworkflows 生态(数十年)无
渲染栈SwiftUIElectron + React旧 web tech系统 native
AI 集成Quick AI(opt-in,硬编码)Raycast AI first-class无系统级
macOS 版本macOS 26+ onlymacOS 13+macOS 12+系统自带
协议AGPL-3.0闭源(OSS 镜像)闭源系统

差异化护城河

  • 技术护城河:React reconciler + JSC + native 渲染(~3000 Raycast extensions 在原生控件下运行,工程复杂度极高)
  • 信任护城河:AGPL-3.0 + zero telemetry + zero account + zero deps(GitHub repo 直接 build,无供应链风险)
  • 无生态护城河:extension 数量级差 Raycast 三个数量级(需要用户迁过来)

竞争风险

最可能被 Raycast 直接采纳同样的 JSC 路线 替代——Raycast 已有 React frontend,内部团队写一个 JSC runtime 完全可能。这时 tinycast 失去技术差异,只剩「更小」一个维度。

生态定位

  • 给「对 Raycast 反感 / 不愿付费 / macOS 26+ 上想试 native」的开发者的 option B
  • 不试图挑战 Raycast 的 platform 地位
  • 填补的是「Raycast 替代品红海 + native + zero deps + privacy-first 蓝海」的精确交叉点
  • 与同名 gettiny.app TinyCast(Mac App Store 付费启动器)需要明确区分——是两款完全不同的产品

套利机会分析

  • 信息差:3.1 个月 7.8k stars,处于早期窗口期中段;中文圈除了 runany.dev / tmdm.cn / conversun 的搬运外几乎没有独立深度评论;DeepWiki/Zread.ai 都被 403/429 拒绝(知识入口整节只能标记未收录)——这是读者层面的真实信息差
  • 技术借鉴:
    • JavaScriptCore + 自研 React reconciler host config 模式(适用于任何「外部 DSL + native 渲染」场景)
    • 四层架构 + 机械 enforce 边界(适用任何中等规模 Swift 项目)
    • Off-by-default capability flags + backup 排除(适用任何工具类 app)
    • Dev 频道 bundle ID 隔离(适用任何 Apple 平台项目)
    • 独立 tool target 拆重型 framework(适用任何需要偶尔 OCR/Vision/PDFKit 的 app)
  • 生态位:「native + zero deps + privacy-first + runs Raycast extensions」是精确的 niche;不是替代 Raycast,是 option B
  • 趋势判断:仍在增长(星均 ~80/天,月均 230+ commit);macOS 26 adoption 上行 + Electron 反感上升都是顺风;后发优势在「可以抄 Raycast 的 React 19 frontend 协议」

风险与不足

  • 单点 bus factor:单人主导 78%+,文档治理完整但代码维护极度依赖 Abue Ammar 个人
  • 作者非软件行业:公司是孟加拉国航空公司,无 prior Apple 生态履历——未来 1-2 年的可持续性需观察
  • macOS 26+ only:用户基数天花板被 macOS adoption 曲线限制;legacymacOS 15 sequoia 分支维护成本
  • AI 集成深度有限:Quick AI 是简化版,无 Raycast Pro 那种深度 agent
  • 生态护城河几乎为零:extension 数量级差 Raycast 三个数量级;用户切换需要「Raycast 反感 + macOS 26 + 愿意重装」三重命中
  • Raycast 反向兼容风险:如果 Raycast 自己也走 JSC 路线,tinycast 失去最大技术差异点
  • 长期 beta 节奏:v0.11.11-beta.107 还卡在 beta,距离 1.0 还有距离

行动建议

  • 如果你要用它:
    • 已经在用 Raycast 且反感账号/Pro 付费/electron → 直接迁,零 extension 损失
    • 在用 Alfred Powerpack 且愿意升级 macOS 26 → 评估迁移(workflows 不能直接迁,snippets/quicklinks 通过 backup 迁)
    • 不愿意升级到 macOS 26 → 留在 Alfred/Raycast,tinycast 不适合
  • 如果你要学它:
    • 重点关注 Tinycast/App/AppCore.swift(composition root,284 行)
    • Tinycast/Features/Extensions/Service/ExtensionRuntime.swift(JSC 嵌入 runtime)
    • Scripts/raycast-runtime/src/reconciler.js(React → JSON tree,~600 行核心创新)
    • Scripts/run-tests.sh(734 行 harness 并发 runner)
    • docs/architecture.md(四层架构 ASCII 图)
    • docs/standards.md(代码风格 + 命名表 + 「Migrate, never wrap」哲学)
    • AGENTS.md(正式工程契约,必读)
    • docs/features/extensions.md(920 行 Raycast 扩展实现,最重要的一篇)
  • 如果你要 fork 它:
    • SwiftPM 化(项目硬性无 SwiftPM 是因 zero-deps 路线,不是设计禁止)
    • 加 Linux 端(JavaScriptCore 跨平台 + 自研 reconciler 理论可移植)
    • AI agent 深度集成(替代「硬编码 Quick AI」,允许 plugin 形式)
    • 增加 extension 作者工具(当前作者体验明显不如 Raycast 团队打磨的工具链)

知识入口

资源链接
DeepWiki未收录(429 拒绝)
Zread.ai未收录(403 拒绝)
关联论文无(应用项目,非学术研究)
在线 Demo无(macOS native 应用,无 web playground)
官网https://tinycast.dev
中文搬运runany.dev/blog/tinycast / tmdm.cn/lightweight-macos-launcher / github.com/conversun/tinycast-cn

相关报告