Puter CLI 实战指南:从终端部署站点与 Worker,管理云端文件与 KV 存储
导读
Puter CLI(@heyputer/cli)是 Puter 官方提供的开发者命令行工具,让你不必离开 Shell 就能管理 Puter 云上的全部资源:一键把静态目录发布为 <subdomain>.puter.site 站点、把单个 JavaScript 文件部署为 <name>.puter.work 的 Serverless Worker、像使用本地 cp/mv/rm 一样操作云端文件系统,还能直接打开一个指向某个 App 或 Worker 的 Key-Value 存储的交互式 JavaScript REPL。读完本文,你将掌握 Puter CLI 的安装、认证、全部子命令的参数与语义,以及其底层与 Puter.js SDK 交互的实现细节,足以在本地与 CI 中完成部署与数据管理。
注意:Puter CLI 当前处于 Beta(0.x)阶段,行为可能在版本间变化。本仓库内对应版本为 src/cli/package.json 中的
0.4.0。
安装
CLI 以 npm 全局包形式分发,包名为 @heyputer/cli,命令入口为 puter。仓库内源码位于 src/cli/,其 package.json 声明了 engines.node >= 18,因此需要 Node.js 18+。
npm install -g @heyputer/cli
安装完成后先登录一次,令牌会被保存下来供后续命令复用:
puter login
login 会打开浏览器完成与 Puter 的认证。登录成功后即可开始部署。命令实现与子命令注册可参考 src/cli/src/commands/ 下的 login.js、site.js、worker.js、fs.js、kv.js 等模块。
认证与会话管理
puter login 走的是浏览器 OAuth 流程。从 src/cli/src/lib/auth.js 的注释可以看到,平台已不再支持用户名/密码登录("username/password login is no longer supported by the platform"),因此只保留 Web 浏览器认证与令牌注入两种方式。
无浏览器环境(如远程服务器):通过 stdin 传入令牌:
echo "$TOKEN" | puter login --with-token
在 src/cli/src/lib/auth.js 的实现中,readStdin() 逐块读取 process.stdin 后去除首尾空白,目的就是让令牌不出现在 argv、Shell 历史或 ps 输出中。
自动化与 CI:设置环境变量 PUTER_AUTH_TOKEN 后,CLI 每次命令都会直接读取它而完全跳过登录:
puter whoami # 显示当前令牌对应的账户
puter logout # 清除本地保存的令牌
令牌的解析顺序在 src/cli/src/lib/config.js 中清晰可见:PUTER_AUTH_TOKEN 优先,其次才是本地存储的令牌。令牌持久化使用 conf 库(projectName: 'puter-cli'),保存形状为未来多账户预留的 { accounts: { default: { token } }, active: 'default' },并尝试将配置文件权限收紧为 0o600(仅属主可读写)。作者在注释中特别说明:纯文本 + 文件权限是部署 CLI 可接受的基准,conf 自带的 encryptionKey 只是混淆而非安全,因此刻意未使用。
logout 仅清除本地存储的令牌(config.js 中的 clearToken()),不会吊销该令牌本身。
站点(Sites):静态托管一键部署
将本地静态目录部署为 <subdomain>.puter.site 地址,然后即可查看、管理或删除自己的站点:
puter site deploy ./dist my-app
不带参数直接运行 puter site deploy 时,CLI 会进入交互模式,逐项提示你输入目录与子域名,并为你建议一个可用的名字。
版本化部署:每次 deploy 都会上传到独立文件夹,因此历史版本会被保留,不会因为重复部署而覆盖旧版本——这让"部署出错回滚到上一版"成为可能。
常用子命令:
puter site deploy [dir] [subdomain] # 部署一个目录
puter site list # 列出你的站点
puter site get <subdomain> # 查看某个站点的详情
puter site delete <subdomain> # 删除一个站点
约束与行为:
- 非交互模式下
dir与subdomain均为必填参数; - 子域名只允许小写字母、数字与连字符(连字符不能位于首尾);交互模式下直接粘贴完整的
my-app.puter.site形式的域名也可以被接受; delete支持-y/--yes跳过确认提示。
站点托管的域名配置可参考 src/cli/src/lib/env.js 中的 SITE_DOMAIN(默认 puter.site),也可通过环境变量覆盖以对接自托管实例(见文末"环境变量"一节)。
Worker:部署 Serverless JavaScript
把单个 JavaScript 文件部署为 Serverless Worker,运行在 <name>.puter.work 地址上。若部署时使用的名字已存在,则原地替换该 Worker 的代码:
puter worker deploy ./api.js my-api
与站点一样,不带参数运行 puter worker deploy 会交互式提示你输入文件名与名字。
puter worker deploy [file] [name] # 部署或替换一个 worker
puter worker list # 列出你的 workers
puter worker get <name> # 查看某个 worker 的详情
puter worker delete <name> # 删除 worker
约束与行为:
- 非交互模式下
file与name均为必填参数; - 名字允许字母、数字与连字符(连字符不能位于首尾);
delete支持-y/--yes跳过确认,命令会删除 Worker 及其背后的文件;- 从你自己的账户部署 Worker 时,Puter 会为它创建一个独立的沙箱 App(见 src/cli/src/lib/appTarget.js 中
resolveWorker()对app_uid的读取),该 Worker 的puter.kv数据就存放在这个 App 的 KV 存储中——这正是下文"用 Worker 名字连接 KV"能够成立的原因。
关于 Worker 事件模型与运行时,可进一步阅读 Worker 文档 与仓库中的 worker/ 运行时实现。
Apps:查看账户下的已注册应用
以下命令为只读操作,用于浏览注册到你账户下的应用:
puter app list # 列出你的 apps
puter app get <name> # 查看某个 app 的详情
该子命令对应的底层 API 封装见 src/cli/src/lib/apps.js。
Files:在终端操作云端文件系统
文件子命令让你从终端直接操作 云存储:列出目录、读取文件、双向拷贝文件/文件夹、移动、删除与查看元信息。
路径模型是本组命令的核心:
- 远端路径带有
puter:前缀,且相对于你的主目录(home)绝对定位; -表示 stdin 或 stdout;- 其余任何形式一律视为本地路径。
因此,传输方向由路径本身决定,而不是由某个 flag 决定:
puter fs ls puter:/Desktop
puter fs cat puter:/notes.txt
puter fs cp -r ./dist puter:/Documents/backup
cp 依据你给出的路径对自动决定行为:
| From(源) | To(目标) | 发生什么 |
|---|---|---|
| 本地 | puter:/… |
上传 |
puter:/… |
本地 | 下载 |
puter:/… |
puter:/… |
在服务器端直接复制,不经由你的机器 |
- |
puter:/… |
将 stdin 写入该文件 |
puter:/… |
- |
将文件字节写入 stdout |
两个本地路径——或任意其他无法判定的配对——会直接报错,而不是替你猜测。mv 仅在你的 Puter 存储内部生效;若要在本机与 Puter 之间"移动",请先 cp、检查副本无误后再删除源文件。
在 src/cli/src/lib/remotePath.js 中可以看到这一模型的落地细节:
HOME_ROOT被定义为'~'(注释说明:SDK 会在服务端展开~,而/在 Puter 上是系统根、列出的是用户名,不是用户键入puter:/时想要的语义);copyDirection()根据local / remote / stdio三种形态的组合返回upload / download / remoteCopy / writeStdin / readStdout,并对local→local、stdio→stdio等歧义组合抛出带 hint 的错误(src/cli/src/lib/remotePath.js);- 远端路径是绝对定位的,不存在"远端工作目录",因此相对路径会被拒绝;同时路径会被钳制在根之下,
..会被拒绝而不是静默改写(assertRemoteOperand,src/cli/src/lib/remotePath.js)。
Piping(管道)
状态信息、进度与提示一律输出到 stderr,数据输出到 stdout,因此可以安全地管道拼接而不必担心混入状态文本:
puter fs ls puter:/logs | xargs -n1 puter fs cat
交互终端中你看到的是可读视图:裸文件名;-l 时对齐的列;传输过程中的进度 spinner。当输出被管道化或重定向时,ls 改为逐行打印完整的 puter: 路径(对应 remotePath.js 的 toOperand(),让每行都能直接喂给下一条命令),而 --json 则打印完整的条目数据——ls 与 stat 均支持。
App 存储(--app)
每个 App 都有自己的存储目录。--app 会让 puter:/ 解析到该目录(而非你的主目录),从而直接读取与编辑某个 App 正在使用的文件。它接受的标识符与 puter kv connect 完全一致——app 名、app uid、worker 名或 worker URL:
$ puter fs ls --app notes puter:/
puter:/settings.json
从 remotePath.js 可见其底层解析目标:~/AppData/<uid>,这与 puter.js 把 App 的相对路径解析到 ~/AppData/<uid> 的规则一致。--app 只是一个 flag:它没有对应的环境变量,也没有可保存的默认值——因为它改变的是"绝对路径指代什么"这种根本语义。任何写入或删除命令都会打印它实际解析到的路径,方便你确认它带来的差异:
$ puter fs rm -r --app notes puter:/cache
rm -r puter:/cache → ~/AppData/app-1f2e3d4c…/cache (412 entries) [notes (app-1f2e3d4c…)]
沙箱是强制的:路径会被限制在该目录内部,puter:/../../Documents 会报错,而不是成为越狱出口。
删除文件
rm 删除单个文件。删除目录需要 -r,它会先打印解析后的路径与包含的条目数,然后在终端中请你确认;在没有交互对象时则必须加 --yes。puter:/ 本身永远被拒绝删除。任何递归操作都支持 --dry-run,只列出将被删除的内容而不会真正删除。
被删除的文件不会进入回收站(Trash)——
puter fs rm是直接移除。
传输(Transfers)
复制文件夹时默认每次并发传输 8 个文件,--concurrency 可调整(范围 1–32);对看起来暂时性失败的单个文件会自动重试。对应实现见 src/cli/src/lib/transfer.js:
- 默认并发
DEFAULT_CONCURRENCY = 8;每个批次上限 100 个文件或 8 MiB(MAX_BATCH_FILES/MAX_BATCH_BYTES,文件在内存中打包,故以数量与体积双上限约束); - 重试上限 3 次,退避基数为 500ms 并逐次翻倍(
ATTEMPTS/BASE_BACKOFF_MS); - 只有 5xx、429 与网络类错误(
ECONN*、ETIMEDOUT、ENOTFOUND、EAI_AGAIN、EPIPE、ENETUNREACH、ESOCKET等)才值得重试,4xx 不会改变主意(isRetryable,transfer.js); - 上传走 SDK 的
puter.fs.upload()签名批量写路径(每次调用是一次签名批量写,而非逐文件请求);失败时服务端会回报failedPaths/failedItems,重试只补发失败的那部分(attributeFailures); - 大于 32 MiB 的远端文件在下载时按 8 MiB 分块(走
offset/byte_count的 Range 请求)以保持内存平稳(CHUNKED_READ_THRESHOLD/READ_CHUNK_BYTES)。
即使仍有文件持续失败,其余文件会继续复制,失败清单在结束时统一列出,命令以非零状态退出——因此一次大型上传不会因为单个文件失败而整体重来。-n / --no-clobber 跳过已存在的文件;不带它时 cp 会覆盖。
Key-Value 存储:交互式 JavaScript 终端
puter kv connect 打开一个指向某个 App 或 Worker 的 key-value store 的交互式 JS Shell,让你绕过应用直接读写它的数据。参数可以是 app 名、worker 名或其 *.puter.work URL,也可以是 uid:
puter kv connect my-app
CLI 会解析对应 App、确认存储可达,然后进入提示符界面:
$ puter kv connect notes
✔ Connected to notes (app-1f2e3d4c…) · 12 keys
kv(notes)> set("greeting", "hi")
true
kv(notes)> get("greeting")
'hi'
kv(notes)> list("gre", true)
[ { key: 'greeting', value: 'hi' } ]
Worker 的接入方式相同。从你的账户部署 Worker 时,它会获得自己独立的沙箱 App,而该 Worker 的 puter.kv 数据正是存放于此——所以输入 worker 名即可连接到它,提示符也会标明当前身处哪个命名空间:
$ puter kv connect my-api
✔ Connected to worker my-api (app-9a8b7c6d…) · 3 keys
kv(worker:my-api)> list()
[ 'visits' ]
名字冲突规则(实现见 src/cli/src/lib/appTarget.js 的 resolveTarget()):名字优先按 App 解析;若 App 与 Worker 同名,你得到的是 App;此时传入 Worker 的 URL(puter kv connect https://my-api.puter.work)即可显式指定 Worker。URL 会先被 workerNameFromUrl() 解析——它只接受扁平的 <name>.puter.work 形态(剥去 scheme、路径、查询串与端口后按 .puter.work 后缀匹配)。一个由 App 部署(而非由你直接部署)的 Worker 没有自己的存储(无 app_uid),这时应改连拥有它的那个 App。
绑定与求值语义:每一个 puter.kv 方法都会被绑定到所连 App,并可以三种形式裸调用——直接方法名、kv.set(…)、puter.kv.set(…),因此从文档中复制的示例可以原样运行。结果会自动 await——get("k") 打印的是值而不是 pending 的 Promise;_ 保存上一次的结果。相关实现见 src/cli/src/commands/kv.js:REPL 复用 Node 内置 node:repl(多行输入、历史、Ctrl-C/Ctrl-D、_ 与 util.inspect 格式皆由 Node 提供),CLI 只在其上叠加两层:结果在回显前被 await;失败只打印单行错误而非堆栈。
这是完整的 JavaScript REPL:多行输入、变量与会话间历史记录都能工作。.help 列出 kv 方法,.clear 重置会话,.exit(或 Ctrl-D)退出。会话历史保存在配置文件目录旁的 kv-history 文件中(kv.js),所以跨会话历史是持久的。连接横幅中的键数量是一次轻量探测(单页取 100 条,满则显示 100+ keys),避免对每个键做计费式的精确统计。
通过
puter kv connect的写入流向所连 App 的存储,而不是你的用户级存储——也就是说,写入的就是该 App 或 Worker 自己读写的那份数据。
非交互上下文下,puter kv connect 会直接以错误退出而非挂起(kv.js:needs a terminal)。
CLI 参考
全局选项
| 选项 | 说明 |
|---|---|
-v, --version |
打印 CLI 版本。 |
-h, --help |
查看任意命令的帮助,例如 puter site deploy --help。 |
CLI 会检测自身是否在交互式环境下运行。终端中它会为缺失的参数逐一提示;非交互环境(CI、输出被管道化,或设置了 CI)中永不提示,此时必填参数必须显式传入。该检测逻辑在 src/cli/src/lib/env.js 中:isInteractive() 要求 stdin 与 stdout 都是 TTY,canPrompt() 只要求 stdin 是 TTY,二者均以 process.env.CI 为硬开关;canAnimate()(spinner/动画/颜色)则仅以 stdout 是否 TTY 判定,因此即使 stdin 仍是终端,输出被重定向到文件时也会关闭动画。
puter login
登录 Puter 并保存令牌供后续命令使用。
| 参数 / 选项 | 说明 |
|---|---|
--with-token |
从 stdin 读取认证令牌,而非打开浏览器。 |
puter logout
清除已保存的认证令牌。无参数。
puter whoami
显示当前令牌对应的账户。无参数。
puter site deploy
将静态目录部署到 <subdomain>.puter.site。
| 参数 | 说明 |
|---|---|
[dir] |
要部署的目录。交互模式下省略时会被提示输入。 |
[subdomain] |
目标子域名。交互模式下省略时会被提示输入;粘贴完整主机名如 my-app.puter.site 也可被接受。 |
非交互模式下两个参数均必填。子域名允许小写字母、数字与连字符(连字符不能位于首尾)。
puter site list
列出你拥有的子域名及其 URL。无参数。
puter site get
查看单个站点的详情。
| 参数 | 说明 |
|---|---|
<subdomain> |
要查看的子域名。 |
puter site delete
删除一个子域名。
| 参数 / 选项 | 说明 |
|---|---|
<subdomain> |
要删除的子域名。 |
-y, --yes |
跳过确认提示。 |
puter worker deploy
将一个 JavaScript 文件部署为 <name>.puter.work 下的 Serverless Worker,或替换已存在的同名 Worker。
| 参数 | 说明 |
|---|---|
[file] |
Worker 的 JavaScript 文件。交互模式下省略时会被提示输入。 |
[name] |
Worker 名字。交互模式下省略时会被提示输入。 |
非交互模式下两个参数均必填。名字允许字母、数字与连字符(连字符不能位于首尾)。
puter worker list
列出你的 Worker 及其 URL。无参数。
puter worker get
查看单个 Worker 的详情。
| 参数 | 说明 |
|---|---|
<name> |
要查看的 Worker。 |
puter worker delete
删除一个 Worker 及其背后的文件。
| 参数 / 选项 | 说明 |
|---|---|
<name> |
要删除的 Worker。 |
-y, --yes |
跳过确认提示。 |
puter app list
列出注册到你账户下的 App。无参数。
puter app get
查看单个 App 的详情。
| 参数 | 说明 |
|---|---|
<name> |
要查看的 App。 |
puter fs ls
列出远端目录。
| 参数 / 选项 | 说明 |
|---|---|
<path> |
要列出的远端路径(puter:/…)。 |
-l, --long |
显示类型、大小与修改时间。 |
--json |
以 JSON 打印完整条目。 |
--app <id> |
让 puter:/ 解析到某个 App 的存储而非你的主目录。 |
puter fs cat
将远端文件内容写到 stdout。
| 参数 / 选项 | 说明 |
|---|---|
<path> |
要读取的远端文件(puter:/…)。 |
--app <id> |
让 puter:/ 解析到某个 App 的存储而非你的主目录。 |
puter fs cp
在本机与 Puter 之间、或 Puter 存储内部复制。
| 参数 / 选项 | 说明 |
|---|---|
<source> |
本地路径、远端路径(puter:/…),或读取 stdin 的 -。 |
<destination> |
本地路径、远端路径(puter:/…),或写 stdout 的 -。 |
-r, --recursive |
复制目录。 |
-n, --no-clobber |
跳过已存在的文件而非覆盖。 |
--concurrency <n> |
同时传输的文件数,1 到 32。默认为 8。 |
--dry-run |
只列出将被复制的内容而不真正复制。 |
--app <id> |
让 puter:/ 解析到某个 App 的存储而非你的主目录。 |
两个路径中必须有一个是远端路径;两个本地路径之间的复制是错误。
puter fs mv
在你的 Puter 存储内部移动或重命名。
| 参数 / 选项 | 说明 |
|---|---|
<source> |
要移动的远端路径(puter:/…)。 |
<destination> |
移动到的远端路径(puter:/…)。 |
--app <id> |
让 puter:/ 解析到某个 App 的存储而非你的主目录。 |
两个路径都必须是远端路径,且 puter:/ 本身不能被移动。
puter fs rm
删除远端文件或目录。
| 参数 / 选项 | 说明 |
|---|---|
<path> |
要删除的远端路径(puter:/…)。 |
-r, --recursive |
删除目录及其全部内容。 |
-y, --yes |
跳过确认提示。非终端下使用 -r 时必须提供。 |
--dry-run |
只列出将被删除的内容而不真正删除。 |
--app <id> |
让 puter:/ 解析到某个 App 的存储而非你的主目录。 |
puter:/ 本身无论是否带 --app 都会被拒绝删除。
puter fs mkdir
创建远端目录。
| 参数 / 选项 | 说明 |
|---|---|
<path> |
要创建的远端目录(puter:/…)。 |
-p, --parents |
创建缺失的父目录;目录已存在时也视为成功。 |
--app <id> |
让 puter:/ 解析到某个 App 的存储而非你的主目录。 |
puter fs stat
查看单个远端文件或目录的详情。
| 参数 / 选项 | 说明 |
|---|---|
<path> |
要查看的远端路径(puter:/…)。 |
--json |
以 JSON 打印完整条目。 |
--app <id> |
让 puter:/ 解析到某个 App 的存储而非你的主目录。 |
puter kv connect
打开针对某个 App 或 Worker 的 key-value store 的交互式 Shell。
| 参数 | 说明 |
|---|---|
<identifier> |
要连接的存储:app 名、app uid(app-…)、worker 名或 worker URL(https://my-api.puter.work)。名字歧义时解析为 App。 |
需要终端——非交互上下文中该命令以错误退出,而非挂起等待。
环境变量
| 变量 | 说明 |
|---|---|
PUTER_AUTH_TOKEN |
代替登录使用的认证令牌,优先级高于已存储的令牌。 |
CI |
设置后 CLI 以非交互方式运行,永不提示。 |
除文档表格外,从 src/cli/src/lib/env.js 的源码还可以看到三个面向自托管部署的端点覆盖变量:PUTER_API_ORIGIN(默认 https://api.puter.com)、PUTER_SITE_DOMAIN(默认 puter.site)与 PUTER_WORKER_DOMAIN(默认 puter.work)。自托管 Puter 实例时,通过这三个变量即可让 CLI 指向你自己的服务端、站点域名与 Worker 域名,而无需改动任何命令。关于自托管的具体步骤,可参考 doc/self-hosting.md。
小结
Puter CLI 的设计处处体现出"面向脚本正确性"的工程取舍:数据走 stdout、状态走 stderr 的管道友好约定;由路径形态而非 flag 决定传输方向的 cp 语义;对歧义配对"响亮报错而非猜测";远端路径绝对定位且钳制在根内(.. 被拒绝);对 puter:/ 根目录的删除保护;上传失败时逐文件续传而不整体重来;以及 PUTER_AUTH_TOKEN / CI 支撑的无人值守运行。若要深入理解命令背后的 API 语义,可继续阅读仓库内的 FS 文档、KV 文档 与 Worker 文档,或直接查看 Puter CLI 源码 与 Puter.js SDK。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00