framejs
framejs 在浏览器中运行一个 ES6 JavaScript 模块。一个 frame 就是一个应用:它存在于一个稳定、可分享的 framejs.app/j/<uuid> URL,其内容是可变的——你可以通过 POST 其哈希参数来原地更新应用。你的任务:将用户的请求转换为那段浏览器 JavaScript,并以适合你环境的方式交付。
选择交付模式
根据你能做什么来选择——而不是根据请求:
- 自动化模式——你可以运行 shell /
node: 生成 JavaScript,然后创建或更新 session 的 framejs.app frame,打印其/j/<uuid>URL,并在浏览器中打开。这是默认模式,只要 shell 可用。 - 代码块模式——仅聊天 / API,无 shell: 仅回复一个单独的、带围栏的 JavaScript 代码块(以
javascript信息字符串开头),除此之外没有任何内容——没有周围文字、没有文件、没有 URL。用户将其粘贴到 framejs.io 的编辑器中。
两种模式下你编写的 JavaScript 遵循相同的规则——请阅读 references/coding-guide.md。
请求可能是什么
- 从提示创建——"一个弹跳球动画"、"绘制 y = sin(x)"。
- 修改现有应用——请求包含一个 frame URL。它可能是一个
https://framejs.app/j/<uuid>或一个https://framejs.io/j/<uuid>?token=…URL("为 AI session 复制 frame"操作会复制 framejs.io 运行时表单),一个原始的 32 字符十六进制 id,或者一个遗留的https://framejs.io/j/<sha256>/ 64 字符 id 快照。每当你看到 framejs URL,从其/j/<uuid>路径中提取 uuid 并定位到那个 frame(将 URL 直接传给--id——即使对于 framejs.io URL,助手也会将更新路由到应用层)。你必须先获取现有代码再修改它——请参阅 references/short-url-api.md(§ 修改)。如果 URL 带有?token=<key>查询参数(来自应用的"为 AI session 复制 frame"操作),则将整个 URL 传给--id(或将 key 传给--token):助手会存储它并将其作为 bearer 凭证发送,这样即使在 frame 的拥有者认领后,你的更新也能继续工作。传递完整 URL 还能定位到该 URL 命名的后端——因此开发或自托管的 frame URL(例如https://framejs-app.localhost:13747/j/<uuid>?token=<key>)会更新其自己的栈,无需预先配置环境。--app-origin <url>/--io-origin <url>可以显式设置后端。请参阅 references/short-url-api.md(§ 本地 / 开发源)。 - 可视化本地文件——请求引用了文件路径(
./data.csv、/tmp/results.json)。上传它们并作为输入传递——请参阅 references/file-inputs.md。
自动化模式——如何交付
生成代码,然后使用捆绑的助手(首选)或 references/short-url-api.md 中的内联 Node 后备方案:
cat << 'JSCODE' | node scripts/framejs.mjs create --state "${SCRATCHPAD}/framejs-frame.json" --title "<简短标题>" --description "<一句话摘要>" --screenshot
// 你生成的浏览器 JavaScript 代码——$vars、反引号、所有特殊字符在 heredoc 内都是安全的
JSCODE
助手在标准输出上打印两个 URL——主要的 https://framejs.app/j/<uuid> 页面 URL 和一个不可变的 snapshot: https://framejs.io/j/<sha256> URL。它还在标准错误输出上打印一条分享链接生命周期通知,你必须将其转达给用户:
/j/<uuid>页面是可编辑且实时更新的,但一个匿名(未认领)的 frame 是临时的,会过期——告诉用户打开它并认领它(免费账户)以永久保留;/j/<sha256>快照是当前应用的不可变副本,在最后一次打开后约 30 天过期——是一个稳定的分享/备份链接,但永远不会反映后续编辑。
对于经典脚本,添加 --module <url>;对于输入,添加 --input name=value。
每个 session 一个 frame,原地更新。 始终传递 --state "<你的 scratchpad 中的路径>/framejs-frame.json"(任何可写文件路径)。第一次 create 会创建 frame;使用相同的 --state 重新运行 create 来更新同一个 frame——/j/<uuid> URL 保持不变,任何打开的 framejs.app 页面都会实时更新,所以只给用户一次 URL。只有当用户想在同一个 session 中创建独立的应用时,才添加 --new 来启动一个新的 frame(后续更新会定位到那个新 frame)。要忽略 state 更新某个特定的 frame,请传递 --id <uuid>。在这种原地更新中,当你没有传递 --og/--title/--description 时,助手会自动向前保留 frame 现有的 Open Graph 数据,因此裸重新运行时永远不会丢失标题/描述(保留的 og.image 会跳过重复截图)。只有在想要更改预览文案时才再次传递 --title/--description。
浏览器仅在 frame 首次创建时自动打开(使用 --no-open 来跳过)。后续对同一个 frame 的更新——相同的 --state 或显式的 --id——不会重新打开浏览器:已经打开的页面通过同 frame 订阅实时接收到更新,再次打开只会产生一个多余的标签页。
如果本地开发检出存在,助手会自动加载其 .env 并定位到开发栈(FRAMEJS_APP_ORIGIN / FRAMEJS_IO_ORIGIN)而不是生产环境——你无需任何操作。
如果助手打印了一条 out of date(过时)更新通知(在标准错误输出上,最多每小时一次),请向用户逐字转达一次——他们安装的技能已落后于最新版本,通知会告诉他们更新所需的唯一命令。
始终传递 --screenshot:助手会渲染完成的应用并保存捕获作为 og:image 预览。它是自保护的——仅在应用还没有 og.image 时捕获,因此不会覆盖之前运行(或用户)已经设置的图像,并且如果渲染器不可用,它会悄悄回退到无图像的 URL。当可以导入时,捕获优先使用 Playwright(真正的 network-idle 等待——最适合从输入获取数据的应用),否则使用系统 headless Chrome。Playwright 是可选的:安装它(npm i -g playwright && npx playwright install chromium)以获得更可靠的路径,或者将 $FRAMEJS_PLAYWRIGHT 指向一个其 node_modules 中包含 Playwright 的目录。通过 --screenshot-wait <ms>(默认 6000——对于加载慢的应用可以提高它)和 --screenshot-size <w,h>(默认 1200,630)调整。用 $CHROME_PATH 覆盖 Chrome 二进制文件。
scripts/framejs.mjs 是相对于此技能目录解析的,而不是你当前的工作目录——从技能文件夹运行它,或使用其绝对路径(Claude Code 将该目录暴露为 ${CLAUDE_SKILL_DIR},因此 ${CLAUDE_SKILL_DIR}/scripts/framejs.mjs 始终有效)。如果你无法定位或运行捆绑的助手,请使用 references/short-url-api.md 中的内联 Node 后备方案——它不需要脚本文件。
始终包含 Open Graph 预览标签,以便链接在分享时能良好地展开——请参阅 references/short-url-api.md 中的 OG 规则:
- 新应用: 使用
--title/--description生成新的文案,并传递--screenshot来捕获预览图像。 - 修改现有应用: 获取到的应用已经带有
og(fetch命令会返回它)。不要重新计算它——直接将获取的对象通过--og '<获取到的 og JSON>'传回,这可以保留所有字段(包括image)。使用--id <uuid>(或相同的--state)更新同一个 frame。只有当用户明确要求更改预览文案时才设置新的--title/--description。你仍然可以传递--screenshot:如果获取到的og已经有image,它不会被修改;如果没有,则会添加新的捕获。
绝对规则(两种模式)
- 仅限浏览器 JavaScript——它在 iframe 中运行,不是 Node.js。
- 必须使用 ES6 模块语法:
export function onInputs(inputs) {}。 - 绝对不要创建 HTML 文件。绝对不要编写本地的
.js文件。绝对不要使用你自己的可视化/渲染/小部件工具来渲染结果。 - 绝对不要修改
root.style.position、root.style.height或root.style.width。 - 重要:可视化必须在手机上看起来良好并能适应屏幕尺寸——使用响应式尺寸(读取
root的尺寸 / 监听 resize),避免固定像素宽度导致溢出,保持小屏幕上的文本和触摸目标清晰易读。 - 在自动化模式下,绝对不要输出让用户复制的代码块,也绝对不要手动构建长哈希 URL 作为交付物——始终通过 frame API(助手的
create)进行 POST。给用户/j/<uuid>页面 URL。 - 在代码块模式下,仅输出单个带围栏的 JavaScript 代码块——没有其他内容。
参考
- references/coding-guide.md——全局变量、导出、模式、CDN 库、常见错误。
- references/short-url-api.md——创建/更新 frame(
POST /j/<uuid>.json)、每 session 一 frame 的状态、Open Graph 标签、API 令牌(在 frame 被认领后继续更新)、内联后备方案。 - references/file-inputs.md——上传本地文件并将其作为输入引入。
scripts/framejs.mjs——Node 助手:create(stdin JS → framejs.app frame)、fetch <id>、upload <path>。源:FRAMEJS_APP_ORIGIN/FRAMEJS_IO_ORIGIN(在本地开发中自动从附近的.env加载),或通过--app-origin/--io-origin或传递给--id/fetch的完整 frame URL 的源进行每次运行设置。