Get Bananas
一款专为 iPhone、iPad、Mac 和 Apple Watch 打造的手写风格购物清单 —— 在你腾不出手时,Claude 也能帮你编辑。
2026 年 4 月发布。全设备 SwiftUI 实现。Mac 应用内附带 Claude Desktop 扩展。29 种语言,每一种都配有手工精选的初始购物数据,让用户首次看到的清单都是地道的本地化版本,而非翻译腔。无服务器、无账户、无 API 密钥。整个后端就是你 iCloud 容器中的一个 JSON 文件。
Claude 可以修改你的购物清单。
Get Bananas 在 Mac 应用内直接附带了一个 Claude Desktop 扩展。在 Mac 上打开「设置」,点击「安装扩展」,Claude Desktop 便会弹出安装提示。确认后打开新对话,Claude 就拥有了五个工具,可以读取和编辑你真实的购物清单。问它「我的清单上有什么?」—— 它会告诉你。说「加上鸡蛋和牛奶」—— 下次 iCloud 同步时,它们就会出现在你的 iPhone 上。
无需注册、无需 API 密钥,也没有任何属于我们的服务器。这个扩展是一个 .mcpb 包,与一个 Node.js MCP 服务器通信,后者读写的是你自己 iCloud Drive 容器中的一个 JSON 文件。
五个工具,每个只做一件事。
工具集刻意做得很小。Claude 可以读取清单、添加一项、删除一项、更新一项,或整体替换。没有花哨的便捷辅助函数,也没有「读取-修改-写入」的封装;工具集贴合你在对话中真正会提出的请求。
- get_shopping_list读取当前清单
- add_item追加一个新项目
- remove_item删除单个项目
- update_item勾选、取消勾选、重命名或修改数量
- update_shopping_list整体替换(例如「新建一份感恩节清单」)
在应用内一键安装。
Mac 应用的「设置」中有一个「安装扩展」按钮。点击后,应用会用 NSWorkspace 打开内置的 get-bananas.mcpb 文件,Claude Desktop 将其注册为扩展,并请用户确认。整个安装过程就是一次 NSWorkspace.shared.open() 调用,作用于应用包内附带的一个文件 —— 因为系统平台已经为我们提供了恰当的原语:一种意为「请把我注册到 Claude」的文件格式。
private func installExtension() { guard let extensionURL = Bundle.main.url( forResource: "get-bananas", withExtension: "mcpb" ) else { extensionMessage = "Error: Extension not found in app bundle." return } let opened = NSWorkspace.shared.open(extensionURL) if opened { extensionMessage = "Check Claude Desktop for the install prompt..." } else { extensionMessage = "Couldn't open the extension. Make sure Claude Desktop is installed." } }
这一模式将对许多原生应用产生深远影响。扩展存放在你的应用包内,随应用一同发布、一同更新 —— 没有独立的注册中心、没有按用户进行的设置、没有「把这个 URL 复制到配置文件里」之类的步骤。如果你希望 Claude 能在你的应用内做某件事,那就把这件事放进应用里,让 OS 来牵线搭桥。
笔尖落纸,永久留存。
购物清单本就是手写在纸上的,用一支笔。这款应用读起来正是这种感觉。分类标题用 Caveat 风格的手写字体,分隔线是细瘦的笔触,未勾选项是空心圆圈。当你点选勾掉某项时,应用会绘制一条手绘的线条划过文字 —— 而每一条线都略有不同。




删除线是一个 <code>Shape</code>,每一道勾选都独一无二。
这条手绘线条是一个自定义的 SwiftUI Shape,由二十段路径组成。每段端点都会获得一个 -1.5 至 +1.5 点之间的随机微小垂直偏移。结果是:每一条在运行时绘制的删除线都与前一条略有不同。在一份二十项的清单上,没有任何两条线完全相同 —— 正如真实笔迹之间从来不会完全一样。
struct HandDrawnStrikethrough: Shape { func path(in rect: CGRect) -> Path { var path = Path() let startY = rect.midY let segments = 20 let segmentWidth = rect.width / CGFloat(segments) path.move(to: CGPoint(x: 0, y: startY)) for i in 1...segments { let x = CGFloat(i) * segmentWidth let randomOffset = CGFloat.random(in: -1.5...1.5) let y = startY + randomOffset path.addLine(to: CGPoint(x: x, y: y)) } return path } }
总共十二行代码。但它也是一款看起来像手写和感觉像手写的应用之间的差别。一张静态的 SVG 删除线在同一天就能交付,像素数也一样,但读起来只会像装饰。
请仔细看看上方两个已勾选的项目。划过「Egg whites」和「Cottage cheese 2%」的线条是不同的形状 —— 不同的颤抖、不同的角度。它们是同一个 Shape 在 200 毫秒之内分别生成的,每次都重新抽取随机数。这就是这款应用的全部身份:把诚实的细微变化烘焙进布局之中。
29 种语言,搭配本地化购物数据。
一位日本用户首次打开的清单,并不是被翻译过来的美式杂货采购单。是 たんぱく質 分类下的 卵白 —— 蛋白,搭配的是你在东京货架上真正会看到的术语。App Store 上有 29 种本地化版本,每一个非英语区域都附带自己手工精选的 SeedData-{locale}.json,其中包含符合当地文化的示例条目和分类名称。阿拉伯语版有 al-burutinat 中的 biyad al-bayd。希伯来语版有 helbonim 中的 helbonei beitza。29 份初始数据文件,全部手写而成,并非通过字符串翻译器一遍批量生成。





RTL 是一份免费的胜利 —— 只要你不再与之对抗。
SwiftUI 把 .leading 和 .trailing 视为语义方向,而不是 .left 和 .right 这种固定方向。只要把界面在语义层面布置一次,阿拉伯语和希伯来语便会自动免费翻转整个布局 —— 分类标题挪到右侧,勾选圆圈挪到文字左侧,「重置」和「更多」按钮转到对角的另一侧。我们没有为此写过一行 RTL 专属代码。
手绘删除线位于这一切之上,且无需感知当前语言区域。这个 Shape 嵌在 ZStack 中,叠加于条目名称的 Text 之上,并跨越那个包围盒进行绘制;环绕的整行布局则由 SwiftUI 的 RTL 机制自动镜像。线条最终会落在它该在的位置 —— 文字之上 —— 而 Shape 代码中没有任何一处针对语言的条件判断。
你 iCloud 容器中的一个 JSON 文件。
跨设备的数据交换格式,就是你自己 iCloud Drive 中的一个文件:BananaList.json,位于 Get Bananas 容器内。MCP 服务器读写的正是这个文件。iOS、iPad、Mac 和 Watch 应用读写的也正是这个文件。同步由 iCloud 处理 —— Apple 提供的免费、静态加密、在「高级数据保护」开启时端到端私密的基础设施。我们这边什么也没有。
// iCloud path for Banana List data const ICLOUD_FILE_PATH = path.join( os.homedir(), "Library/Mobile Documents/iCloud~com~941apps~Banana-List/Documents/BananaList.json" ); // Lock file for atomic operations const LOCK_FILE_PATH = ICLOUD_FILE_PATH + ".lock"; const LOCK_TIMEOUT_MS = 5000;
MCP 服务器接触到的每一条路径都是这个 JSON 文件的兄弟节点。锁文件就在它旁边(.lock,原子写入,五秒超时后若已陈旧便自动清理)。写入会先落到一个带进程标记的临时文件(.tmp.{pid}),然后原子地重命名到真实路径上。整套并发模型 —— Claude 与正在运行的应用之间 —— 就装在一个获取/释放函数里。
在每台设备上,SwiftData 是本地存储。iCloud 上的 JSON 是传输格式。SwiftData 在设备上保持快速可查询;JSON 则保持简洁、易读,便于同步以及让 Claude 操作。两个存储,每个只做一件事 —— 谁也不试图扮演两种角色。
这让我们能够做出哪些承诺。
没有 941 Apps 服务器。没有分析。没有登录。没有 Claude 的 API 密钥 —— 你已经在 Claude Desktop 里完成了认证。没有订阅。这个扩展是开源的,任何好奇的人都可以读完整的数据路径:就是那个 JSON 文件,它从头到尾只触碰这一个文件。你的购物清单永远不会经由我们的任何基础设施离开你的 iCloud 容器。
工作就是这份清单。
购物清单只有一项工作。在你想到某样东西的瞬间记下它,找到时勾选掉它,剩下的时间让它别挡你的路。产品就是这项工作。它不是食谱应用、不是膳食规划、不是条形码扫描器、不是比价工具,也不是库存管理。清单本身就是交付物。
Claude 集成之所以理直气壮地占据一席之地,正是因为它能在「用手机打字是错误工具」的场景下,加速记录与编辑:站在炉灶前两手沾满蒜味时,边走边大声计划本周的伙食时,从一张菜谱卡片上抄录十二项时不必逐字重打。把这些场景委托给 Claude,能让应用其余部分始终保持小巧。
凡是不属于「清单、记录、勾选或这种委托」的东西,都是另一款产品。不同的产品应该归属不同的应用。
Get Bananas 中没有的东西。
大多数购物清单应用的路线图最终都通向「成为 Instacart」。这一款不是。Get Bananas 出于自觉,拒绝长成一个杂货平台。下面是它明确<em>不</em>做的事情清单:
- 无条形码扫描
- 无价格、无总额、无预算管理
- 无食谱导入或膳食规划
- 无货架导航或商店地图
- 无网页版
- 无账户、无登录、无邮箱采集
- 不与他人共享清单(仅在你自己的 iCloud 中、跨你自己的设备)
- 永远不做分析
- 无广告、无订阅、无 Pro 版
- 无打扰式弹窗,无应用内推销
Get Bananas 售价 0.99 美元。一次买断,永久拥有。MCP 服务器是开源的。数据归你所有,存放在你自己的 iCloud Drive 里。它的工作就是快速记录、快速勾选,以及在你两手沾满香菜时,把编辑工作委托给 Claude。其它的一切,都是别人家的应用。
Get Bananas.
现已上架 App Store,支持 iPhone、iPad、Mac 和 Apple Watch。在 Mac 应用内即可安装 Claude Desktop 扩展。