GitHub 推荐:3 个月 7.8K stars:Abue Ammar 的 tinycast 怎么用 SwiftUI + JavaScriptCore 把 Raycast 生态偷走
一句话总结
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 平台项目的工程实践
项目展示
— hero: 命令面板主界面
— feature: 内置计算器扩展(跑 Raycast bundle,原生 SwiftUI 渲染)
— feature: 剪贴板历史(独立 tool target fork OCR,每条 ~50ms)
— feature: 应用专属快捷键
— feature: 内存占用(<100 MB vs Raycast ~500 MB)
项目画像
| 维度 | 数据 |
|---|---|
| GitHub | https://github.com/abue-ammar/tinycast |
| Star / Fork | 7,778 / 400(仅 3.1 个月) |
| Watcher / Open Issue | 8 / 多条活跃(#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 基本 |
| License | AGPL-3.0(README 明示;GitHub auto-detect 为 Other) |
| Release | v0.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 分支
核心价值提炼
创新之处
- 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 控件 - 「capability flag ≠ preference」(4/5/5):
snippetsEnabled不进 settings backup——因为 backup 文件能发给别人,import 等于「无需用户操作就在别人 Mac 上装键盘监听器」 - GitHub tree API + recursive=1 装 extension(3/5/4):不走
contentsAPI(cap 1000 + silent truncate + 60 calls/hour),走 trees API 单 recursive listing——3 calls vs 18+ - 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 才「看起来相等」
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/- URL scheme 抢注(4/4/3):抢注
raycast://、com.raycast://、tinycast://三个 scheme;ExtensionHostBridge拦截内部 URL 让open("raycast://")重新打开 palette 而非启 Raycast - Harness 编译 ship sources(build-blocking 边界检查,3/5/5):
Tests/<name>.swift一行run name src1 src2 ...,让import AppKit出现在 Model/ 就是 build error——比 linter rule 更可靠
可复用的模式与技巧
- Pure/Effect/Observable/View 四层分离 + 机械 enforce:
grep拒收import AppKit|SwiftUI在 Model/;harness 编译 ship sources 而非副本;broken build = 边界 leak 信号 - Off-by-default capability flags + backup 排除:每个 capability 一个
Bool开关 + 启用 confirm dialog;capability 不进 settings backup,只进 content - Dev 频道隔离 bundle ID:
com.app.dev完全独立 prefs/caches/TCC grants/login item——开发 build 不会污染用户安装的 stable 副本 - JavaScriptCore 嵌入 runtime(generated, committed):
Resources/Runtime.generated.js在 bundle 里直接 load,building 不需要 Node set -e+ AND-OR list 陷阱:CI 跑过了一个 25 phase 没编译的 harness——run-tests.sh把 compile 和 run 步骤分开记录失败withObservationTracking一次性陷阱:AppCore.track是 reference pattern——onChange 在 write 之前 fire,必须 defer re-read 进 Task + 在 Task 里 re-arm 追踪runObservingExit替代waitUntilExit:后者在 GCD thread 会 hang(run loop 错过 exit)- Block observer 走 RAII
NotificationToken:deinit移除 observer,不靠调用方记得配对 - 独立 tool target 拆重型 framework:Clipboard OCR 用独立 bundle ID 嵌入
Contents/Helpers,每条 item spawn 一个 helper → 拿文本 → exit;主 app 守得住 100 MB Signposts.interval必须 defer 包裹 work:throw 会跳过.endemit → 显示永不关闭的 interval
关键设计决策
-
决策: 零三方依赖路线(zero third-party deps)—— README 强声明
- 问题:每个 dep 都带来供应链攻击面 + 版本协调成本 + 二进制膨胀
- 方案:任何东西要自己实现(JS runtime、React reconciler、ZIP 解压、文件 IO)
- Trade-off:开发时间 vs 体积 & 控制力 & 信任
- 可迁移性:低(需要产品确实能 hold 住开发时间)
-
决策: 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 渲染」组合)
-
决策: Off-by-default capability flags
- 问题:一个 macOS launcher 装上就监听键盘、扫文件、跑第三方代码
- 方案:每个 capability 一个
Bool开关 + 启用 confirm dialog;capability 不进 settings backup - Trade-off:用户首次体验少 features → 换「imported file 不能给你装个键盘监听器」这种安全保证
- 可迁移性:高——任何工具类 app 都可借「capability ≠ preference」
-
决策: Swift 6 strict concurrency +
@MainActor默认 + 1 个 actor- 问题:默认
@MainActor防止 data race;额外 actor 会增加协调复杂度 - 方案:重 IO 显式
nonisolated static+Task.detached;cross-actor model typesSendable;@unchecked Sendable必须有 written reason - Trade-off:比「多个 actor + message passing」多写一点样板 → 换「一处 reasoning location」
- 可迁移性:现代 Swift 项目标配
- 问题:默认
-
决策: 「latest-only, always」—— macOS 26+ only + Swift 6 strict concurrency + 无 SwiftPM
- 问题:兼容性 shim 累积失控(standards.md 第 18-22 行明示)
- 方案:删除版本门控代码比 feature 本身还大 → 不做版本门控
- Trade-off:用户基数有限(macOS 26 only)→ 换 codebase 不变胖
- 可迁移性:中——需要产品确实没有遗留 OS 约束
竞品格局与定位
竞品对比矩阵
| 维度 | tinycast | Raycast | Alfred | Spotlight |
|---|---|---|---|---|
| 体积 | ~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 + Store | workflows 生态(数十年) | 无 |
| 渲染栈 | SwiftUI | Electron + React | 旧 web tech | 系统 native |
| AI 集成 | Quick AI(opt-in,硬编码) | Raycast AI first-class | 无 | 系统级 |
| macOS 版本 | macOS 26+ only | macOS 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.appTinyCast(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 |