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 文件。
update_shopping_list,起草替换内容,并在任何更改生效前准备好调用。
五个工具,每个只做一件事。
工具集刻意做得很小。Claude 可以读取清单、添加一项、删除一项、更新一项,或整体替换。没有花哨的便捷辅助函数,也没有「读取-修改-写入」的封装;工具集贴合你在对话中真正会提出的请求。
- get_shopping_list读取当前清单
- add_item追加一个新项目
- remove_item删除单个项目
- update_item勾选、取消勾选、重命名或修改数量
- update_shopping_list整体替换(例如「新建一份感恩节清单」)
在应用内一键安装。
Mac 应用的「设置」中有一个「安装扩展」按钮。点击后,应用会用 NSWorkspace 打开内置的 get-bananas.mcpb 文件,Claude Desktop 将其注册为扩展,并请用户确认。整个安装过程就是一次 NSWorkspace.shared.open() 调用,作用于应用包内附带的一个文件 —— 因为系统平台已经为我们提供了恰当的原语:一种意为「请把我注册到 Claude」的文件格式。
.mcpb 注册到 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 扩展。