1. 项目概述Photon一次定义处处交付最近在折腾AI工具链和内部自动化时我一直在寻找一种能统一逻辑、简化接口开发的方案。传统的做法是一个核心功能你得为它写一个CLI脚本、再搭一个简单的Web界面、最后还得想办法让AI助手比如Claude、Cursor也能调用它。这三套代码虽然逻辑相似但实现起来却要重复劳动维护成本也高。直到我遇到了Photon它彻底改变了我的工作流。简单来说Photon让你只写一个TypeScript类文件然后自动为你生成三样东西一个功能完整的CLI命令行工具、一个自动渲染的Web界面叫Beam以及一个标准的MCP服务器能让你的代码直接被Claude Desktop、Cursor等AI客户端调用。它的核心理念是“意图优先”。你不需要操心HTTP路由、命令行参数解析或者MCP协议细节你只需要用TypeScript清晰地定义你的“意图”——也就是你的业务逻辑和方法。剩下的Photon全包了。这种“一次定义多处交付”的模式对于快速构建内部工具、AI Agent插件或是个人自动化脚本来说效率提升是颠覆性的。2. 核心设计理念与架构拆解2.1 为什么是“单一文件”哲学Photon最吸引我的设计就是它的“单一文件”哲学。你所有的逻辑、配置、甚至UI描述都集中在一个.photon.ts文件里。这听起来简单但背后有深刻的考量。首先它极大地降低了认知负担和启动成本。你想做一个天气查询工具不需要初始化一个Node.js项目、配置tsconfig.json、安装express和commander。你只需要创建一个文件写一个类几个方法就完成了。Photon内置了TypeScript编译器你连tsc都不用管。其次它强制了逻辑的纯粹性。因为所有接口都源自同一个定义所以CLI、Web UI和AI Agent看到的是完全一致的API契约。你在JSDoc里写的参数描述既会成为Web表单的提示文字也会成为AI调用时的工具描述。这种“单一事实来源”从根本上杜绝了接口不同步的问题。最后它让分发和复用变得极其简单。一个Photon文件本身就是一个完整的、可分发的“包”。你可以通过photon add命令从GitHub直接安装他人的Photon或者通过photon build把它编译成单个可执行文件。这种原子化的封装非常符合现代“可组合工具”的理念。2.2 三层接口的自动推导机制Photon如何从一个类推导出三个接口这是其架构的精妙之处。它本质上是一个强大的元编程和代码分析工具。CLI接口生成Photon会解析你的类方法签名。每个公有方法都会成为一个CLI子命令。方法的参数会根据其类型string,number,boolean等被映射为命令行选项--name Ada。复杂的对象参数会被展开。它底层利用了类似yargs的库但配置是完全自动生成的。Web界面生成这是Beam的功劳。Beam会分析你的方法签名、JSDoc注释和参数标签Tags动态渲染出一个表单界面。string类型且带有{format email}标签的参数会变成邮箱输入框并自带验证。number类型带有{min 1} {max 7}标签会变成带范围限制的数字输入框。param status {choice pending,completed,failed}会让参数变成一个下拉选择框。方法上的format table标签会让返回的数组数据以表格形式展示。这一切都无需你写任何HTML、CSS或前端框架代码。MCP服务器生成这是连接AI世界的桥梁。Photon会将你的类和方法按照Model Context Protocol规范包装成一个MCP服务器。AI客户端如Claude Desktop通过标准MCP协议与这个服务器通信获取可用的工具列表即你的方法并在需要时调用它们。你写的JSDoc注释会直接成为AI理解工具功能的“说明书”。这三层共享同一套核心类型验证。你在TypeScript中定义的类型和通过标签添加的约束如正则表达式{pattern}会在CLI解析、Web表单提交和MCP调用时被统一强制执行。这确保了数据流入逻辑层之前就是干净、合规的。2.3 状态、事件与协调超越简单CRUDPhoton不仅仅是一个接口生成器它还内置了构建复杂应用所需的基础设施其中最关键的两个概念是锁和事件。分布式锁通过locked装饰器标记的方法在任何时刻都只允许一个调用者执行。这个锁是跨进程的。想象一个场景你有一个“部署生产环境”的方法。一个人类用户通过Web UI点击了部署同时一个AI Agent也尝试触发部署。如果没有锁可能会导致灾难性的并发操作。locked确保了操作的序列化无论是来自Web、CLI还是AI的请求都乖乖排队。实时事件这是实现交互式、实时应用的关键。在Photon方法内部你可以调用this.emit({ event: eventName, data: ... })。在前端无论是自动生成的Beam还是你的自定义UI你可以通过一个全局注入的对象以你的Photon命名如chess.on(eventName, handler)来监听这些事件。底层Photon使用Server-Sent Events通过MCP的流式HTTP传输协议来推送消息你无需自己搭建WebSocket。两者结合锁和事件共同实现了“带实时状态同步的协调工作流”。文档里举的象棋例子非常贴切move方法被locked确保了人类和AI轮流走棋。每走一步服务器就emit一个boardUpdated事件所有连接的客户端Web界面、AI对话界面的棋盘都会实时更新。这在构建审批流、协作编辑、实时监控面板等场景下威力巨大。3. 从零开始创建你的第一个Photon理论说了这么多我们动手创建一个。假设我们要做一个简单的“待办事项”管理器。3.1 环境准备与安装首先确保你的系统安装了Node.js 20或更高版本。然后通过npm全局安装Photonnpm install -g portel/photon安装完成后运行photon --version检查是否成功。你也可以使用npx来避免全局安装但为了方便演示我们使用全局命令。3.2 创建项目骨架在你喜欢的工作目录下运行以下命令来创建一个新的Photonphoton new todo这个命令会在当前目录生成一个名为todo.photon.ts的文件里面已经包含了一个简单的示例类。我们打开它将其替换为我们自己的内容。3.3 编写核心逻辑将todo.photon.ts的内容修改如下/** * 简单的个人待办事项管理器 * icon */ export default class Todo { // 使用内存存储实际项目中可使用 this.memory 或数据库 private items: Array{ id: number; task: string; completed: boolean } []; private nextId 1; /** * 添加一个新的待办事项 * param task 任务描述 {example 购买 groceries} */ add(params: { task: string }): { id: number; task: string } { if (!params.task.trim()) { throw new Error(任务描述不能为空); } const newItem { id: this.nextId, task: params.task, completed: false, }; this.items.push(newItem); // 触发一个事件通知前端列表已更新 this.emit({ event: listUpdated, data: this.items }); return { id: newItem.id, task: newItem.task }; } /** * 列出所有待办事项 * param showCompleted 是否显示已完成事项 {default false} * format table */ list(params: { showCompleted?: boolean } {}): Array{ id: number; task: string; completed: boolean } { const { showCompleted false } params; if (showCompleted) { return this.items; } return this.items.filter(item !item.completed); } /** * 标记一个待办事项为完成或未完成 * param id 待办事项的ID * param completed 完成状态 */ mark(params: { id: number; completed: boolean }): { success: boolean; message: string } { const item this.items.find(item item.id params.id); if (!item) { return { success: false, message: 未找到ID为 ${params.id} 的待办事项 }; } item.completed params.completed; this.emit({ event: listUpdated, data: this.items }); return { success: true, message: 事项 ${item.task} 已标记为 ${params.completed ? 完成 : 未完成} }; } /** * 清理所有已完成的事项 * locked */ clearCompleted(): { removed: number } { const initialLength this.items.length; this.items this.items.filter(item !item.completed); const removed initialLength - this.items.length; this.emit({ event: listUpdated, data: this.items }); return { removed }; } }代码解读与注意事项JSDoc是UI和AI的桥梁注意看add方法中param task后面的描述和{example}。这个描述会直接显示在Beam Web界面的输入框标签上而{example}会提供一个占位符示例。同时当AI如Claude查看这个工具时它读到的也是这段描述从而知道该如何使用。format table在list方法上我们添加了format table标签。这意味着当通过Beam Web界面或CLI调用该方法时返回的数组会自动以美观的表格形式呈现而不是原始的JSON。lockedclearCompleted方法被标记为locked。这意味着即使这个Photon的多个实例同时运行或者被多个用户同时调用清理操作也会被序列化防止数据竞争。this.emit我们在add、mark、clearCompleted方法中都调用了this.emit发送listUpdated事件。这样任何连接到这个Photon的Web界面后面会看到都能实时收到列表变更的通知实现无刷新更新。错误处理在add方法中我们对空任务进行了校验并抛出错误。Photon会自动捕获这些错误并以结构化的方式返回给调用方CLI、Web或AI包括错误类型和消息。3.4 运行与体验三种接口现在让我们看看这一个文件如何变成三个工具。1. 启动Web界面photon执行后默认会在浏览器打开http://localhost:3008这就是Beam的仪表盘。你应该能看到刚刚创建的Todo光子。点击进入你会看到一个完全根据你的代码生成的界面有“添加任务”的表单带输入框和示例提示、“列出任务”的按钮带一个“显示已完成”的复选框、“标记完成”的表单需要输入ID和选择状态以及“清理已完成”的按钮。尝试添加几个任务然后标记完成一两个。你会发现操作非常流畅list方法返回的数据自动渲染成了表格。这就是“意图即界面”。2. 作为CLI工具使用打开另一个终端窗口。# 列出所有未完成的任务 photon cli todo list # 列出所有任务包括已完成 photon cli todo list --showCompleted true # 添加一个新任务 photon cli todo add --task 学习Photon的高级功能 # 标记ID为1的任务为完成 photon cli todo mark --id 1 --completed true # 清理已完成的任务由于有locked这个操作是安全的 photon cli todo clearCompletedCLI的输出是格式化的JSON或表格取决于format非常适合集成到脚本中。3. 作为MCP服务器供AI调用首先我们需要以MCP服务器模式运行Photonphoton mcp todo这个命令会启动一个MCP服务器。为了让AI客户端如Claude Desktop能连接它我们需要获取连接配置。photon info todo --mcp这个命令会输出一段JSON配置类似于{ mcpServers: { todo: { command: photon, args: [mcp, todo] } } }接下来你需要将这段配置添加到你的AI客户端的MCP服务器配置中。Claude Desktop: 配置文件通常位于~/Library/Application Support/Claude/claude_desktop_config.json(Mac) 或%APPDATA%\Claude\claude_desktop_config.json(Windows)。在mcpServers对象中添加上述配置。Cursor: 在设置中搜索MCP进行配置。配置完成后重启你的AI客户端。现在当你与AI对话时它就能“看到”并调用你的Todo工具了。你可以对AI说“帮我添加一个待办事项写一篇关于Photon的博客”AI会理解并使用add方法。这就是“一次定义AI亦可调用”的魅力。注意首次以MCP模式运行时Photon可能会提示安装一些必要的npm依赖如果你在代码中声明了dependencies。这是一个非常贴心的功能确保了环境的自包含性。4. 进阶功能与实战技巧掌握了基础用法后我们来探索一些能让你的Photon变得更强大的高级特性。4.1 依赖管理与环境配置真实的工具往往依赖外部包或需要配置。Photon通过构造器和标签优雅地处理这些。声明NPM依赖如果你的Photon需要用到axios或date-fns这样的库你不需要用户手动npm install。在类级别使用dependencies标签即可。/** * 天气查询工具 * dependencies axios^1.6.0 * cli curl -V # 声明需要系统安装curl */ export default class Weather { constructor(private apiKey: string) {} async forecast(city: string) { // axios 会被自动安装直接可用 const response await axios.get(https://api.weatherapi.com/v1/forecast.json?key${this.apiKey}q${city}); return response.data; } }当用户第一次运行这个Photon时Photon会自动检测并安装axios。cli标签则用于声明系统级的命令行依赖如果未找到Photon会给出明确的安装指引。通过构造器注入配置构造器的参数会自动映射为配置项。对于上面的Weather类apiKey会成为一个必需的配置。在Beam中它会出现在“设置”面板作为一个密码输入框。在CLI中你需要通过环境变量WEATHER_API_KEY来提供它或者在首次运行时通过交互式提示输入。在MCP上下文中配置通常通过环境变量或宿主如Claude Desktop的配置机制来管理。这种设计将敏感信息如API密钥与代码逻辑分离符合安全最佳实践。4.2 构建自定义交互界面虽然Beam的自动表单很强大但有时你需要更复杂的UI。Photon支持完全自定义的HTML界面。步骤在Photon文件同目录下创建ui文件夹。在ui文件夹内创建HTML文件例如ui/weather.html。在Photon类或方法上使用ui ./ui/weather.html标签来关联这个界面。关键机制Photon Bridge API在你的自定义HTML中Photon会自动注入一个全局对象其名称与你的Photon文件同名例如weather。通过这个对象你可以调用服务器方法weather.forecast({city: Beijing}).then(...)监听服务器事件weather.on(forecastData, (data) { ... })接收实时渲染如果服务器方法中调用了this.render(...)内容会推送到前端。示例一个简单的自定义天气界面!-- ui/weather.html -- !DOCTYPE html html body input idcityInput placeholder输入城市 button onclickgetForecast()查询/button div idresult/div script // weather 对象由Photon自动注入 function getForecast() { const city document.getElementById(cityInput).value; weather.forecast({ city }) .then(data { document.getElementById(result).innerHTML pre${JSON.stringify(data, null, 2)}/pre; }) .catch(err { document.getElementById(result).innerHTML 错误: ${err.message}; }); } // 监听服务器事件如果需要 weather.on(newAlert, (alert) { console.log(天气警报:, alert); }); /script /body /html这样当你在Beam中点击这个Photon时就会加载这个自定义界面而不是默认的表单。这个UI也可以作为MCP App嵌入到支持的应用如新版Claude Desktop中。4.3 计划任务与WebhookPhoton让定时任务和对外提供HTTP接口变得异常简单。计划任务使用scheduled标签你可以让任何方法按Cron表达式定时运行。export default class DataBackup { /** * 每天凌晨2点备份数据库 * scheduled 0 2 * * * */ async backupDatabase() { // 执行备份逻辑... this.emit({ event: backupCompleted, data: { timestamp: new Date() } }); } }通过photon ps命令你可以查看和管理所有计划任务的状态。Webhook使用webhook标签可以将一个方法暴露为HTTP端点供外部服务如GitHub、Zapier调用。export default class GithubBot { /** * 接收GitHub Webhook * webhook POST /github/webhook */ async handleWebhook(payload: any) { if (payload.action opened payload.pull_request) { // 自动给新PR添加标签 await this.addLabel(payload.pull_request.number, needs-review); } return { received: true }; } }启动Photon后这个端点就可以通过http://localhost:3008/webhook/github/webhook访问。Photon会自动处理HTTP请求的解析、验证可结合OAuth并将payload传递给方法。4.4 状态持久化与跨Photon调用持久化存储每个Photon实例都有一个内置的、隔离的键值存储this.memory。它基于SQLite数据在Photon守护进程重启后依然存在非常适合存储用户偏好、会话状态或小型数据集。export default class UserPrefs { async setTheme(theme: light | dark) { this.memory.set(userTheme, theme); return { success: true }; } async getTheme() { return { theme: this.memory.get(userTheme) || light }; } }跨Photon调用在一个Photon中你可以通过this.call()方法调用另一个Photon的方法实现服务编排。export default class WorkflowOrchestrator { async runDailyReport() { // 1. 从数据库Photon获取数据 const data await this.call(analytics, getDailyMetrics); // 2. 用图表Photon生成图表 const chart await this.call(chart-generator, createChart, { data, type: line }); // 3. 通过邮件Photon发送报告 await this.call(email-sender, sendReport, { chart, recipients: [teamexample.com] }); return { status: report sent }; } }这允许你将复杂的功能拆分成多个单一职责的Photon然后通过一个协调者Photon将它们组合起来符合微服务的设计思想。5. 生产环境部署与问题排查5.1 编译为独立二进制文件对于分发你可以将Photon编译成单个可执行文件这样目标机器上甚至不需要安装Node.js。# 编译当前平台的二进制文件 photon build todo # 交叉编译例如在Mac上编译Linux版本 photon build todo -t bun-linux-x64 # 编译并嵌入Beam UI形成一个独立的桌面应用 photon build todo --with-app编译功能基于Bun的打包器。生成的二进制文件包含了Photon代码、所有声明的dependencies以及Photon运行时体积小巧部署方便。5.2 部署选项Photon本身是一个Node.js应用部署方式非常灵活长期运行Systemd/Docker将photon mcp your-photon作为后台服务运行。你可以编写一个systemd service文件或Dockerfile。记得处理好配置环境变量和持久化数据~/.photon目录的挂载。Serverless实验性通过适配器可以将Photon部署到Cloudflare Workers或AWS Lambda。这需要将Photon的HTTP服务器逻辑适配到Serverless环境。社区正在积极完善这方面的支持。静态托管Serverless函数一种更前沿的模式是将自定义UIHTML/JS部署到Netlify/Vercel等静态托管而将Photon的后端方法作为Serverless函数部署两者通过Photon Bridge API通信。这能实现前后端分离的部署。5.3 常见问题与排查技巧在实际使用中你可能会遇到以下问题1. 启动失败Error: Cannot find module原因通常是因为dependencies声明的npm包尚未安装。解决首次运行Photon时它会尝试自动安装。如果失败可以手动进入Photon文件所在目录运行npm install package-name。也可以运行photon doctor检查环境。2. MCP客户端连接失败原因Claude Desktop/Cursor配置错误或Photon的MCP服务器未在运行。排查确保已运行photon mcp name。运行photon info name --mcp仔细核对输出的JSON配置并完整复制到客户端的配置文件中。检查客户端日志。Claude Desktop的日志通常包含MCP连接错误信息。确认端口是否冲突默认是动态的由MCP over stdio通信一般无端口问题。3. 自定义UI无法加载或photon对象未定义原因HTML文件路径错误或Photon Bridge API脚本未正确注入。解决确认ui标签路径正确且HTML文件位于ui/目录下。在浏览器开发者工具中检查Console和Network标签页看是否有404错误。确保你的HTML中没有其他JS库覆盖了全局的photon对象通常以你的Photon文件名命名。4.scheduled任务没有执行原因计划任务需要Photon守护进程处于运行状态。解决通过photon ps命令查看守护进程和任务状态。确保你是通过photon命令启动的Web界面或photon mcp启动的MCP服务器而不是一次性CLI命令。计划任务只在长期运行的过程中生效。5. 性能或内存问题原因Photon为每个连接的客户端Web浏览器、AI会话维护独立的实例状态。如果方法中有繁重操作或内存泄漏可能导致问题。优化对于计算密集型任务考虑使用locked防止并发或拆分成更小的光子。使用this.memory存储大量数据时需谨慎定期清理。使用photon ps监控运行中的会话和资源占用。调试建议使用photon mcp name --dev模式启动它支持热重载并在控制台输出更详细的日志。在代码中使用console.log进行调试输出会显示在运行Photon的终端里。查阅Photon生成的日志文件通常位于~/.photon/logs目录下。Photon是一个正在快速发展的项目它的理念是将开发者从重复的接口开发中解放出来专注于核心逻辑。虽然在某些边缘场景或深度定制化需求上可能遇到限制但对于构建原型、内部工具、AI增强应用以及需要多端接口的服务来说它的效率和简洁性是无与伦比的。我最欣赏的一点是它用极简的约定和强大的元编程创造了一种全新的、以意图为中心的开发体验。当你习惯了这种模式后就很难再回到那种为每个接口单独编写样板代码的日子了。