关于 psh.now
psh.now 是面向 AI Agent 的审批服务,会暂停 Agent 请求,直到有人批准、拒绝或回复。它适用于能运行命令行或发起 HTTP 请求的 Agent,也支持可自托管部署。
主要功能
- 01Agent 请求的人类审批
- 02通过手机批准或拒绝
- 03自由文本回复请求
- 04命令行与 HTTP API
- 05Claude Code 工具调用前钩子
- 06可自托管部署
适用场景
- 01执行破坏性命令前要求审批
- 02通过手机审核生产环境操作
- 03在 Agent 运行时回答问题
- 04拦截 Claude Code 的 Bash 命令
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://psh.now/mcpclaude mcp add --transport http passy-ai-psh-now https://psh.now/mcp{"mcpServers":{"passy-ai-psh-now":{"type":"http","url":"https://psh.now/mcp"}}}在 Cursor 中打开{"servers":{"passy-ai-psh-now":{"type":"http","url":"https://psh.now/mcp"}}}在 VS Code 中打开{"mcpServers":{"passy-ai-psh-now":{"type":"http","url":"https://psh.now/mcp"}}}{"mcpServers":{"passy-ai-psh-now":{"serverUrl":"https://psh.now/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
https://psh.now/mcp需要授权工具
详细介绍
psh.now
为 AI Agent 提供人工审批:一个主机、一个密钥、一个布尔值。

需要决策的 Agent 会向 psh.now 发送简短的标题和消息,然后等待。你可以在一个页面上轻点批准或拒绝,也可以用自己的话作答,Agent 会根据结果继续运行。它的存在,是因为 Agent 经常会卡在无人查看的终端中。
- 适用于任何能运行 shell 命令或发送 HTTP 请求的 Agent。无需 SDK,也无需 MCP。
- 一个页面管理所有 Agent 和所有机器,包括无法连接的沙盒。
- 可自托管:采用 MIT 许可,需运行两个容器并设置三个环境变量。
┌────────────┐ blocking POST / ┌────────────────┐ WebSocket ┌─────────┐
│ AI agent │ ───────────────────────► │ psh.now gateway │ ─────────────► │ phone │
│ (terminal) │ │ (Elysia + │ │ (PWA) │
│ │ ◄─────────────────────── │ Postgres) │ ◄───────────── │ │
└────────────┘ { approved: true } └────────────────┘ decision └─────────┘
请求包含标题和消息,不只是 shell 命令:可以是要运行的命令、要执行的操作,也可以是要回答的问题。
接下来
| 我想要 | 阅读 |
|---|---|
| 安装并处理我的第一个请求 | 快速入门 |
| 在自己的服务器上运行 | docs/self-hosting.md 或任意实例上的 /self-hosting |
| 从头到尾了解工作流程 | docs/how-it-works.md |
| 直接调用 API | HTTP API |
| 将提示词粘贴到 Agent 中 | docs/prompt-templates.md |
| 报告漏洞 | SECURITY.md |
| 参与贡献 | CONTRIBUTING.md |
技术栈
| 层级 | 技术 |
|---|---|
| 运行时 | Bun |
| HTTP / WS | Elysia |
| 数据库 | PostgreSQL + Drizzle ORM |
| 实时通信 | WebSockets + Postgres LISTEN/NOTIFY(多节点扇出) |
| 前端 | 无需构建的 PWA(原生 ES 模块 + 服务工作线程) |
快速入门
# 1. install
bun install
# 2. configure
cp .env.example .env # then set DATABASE_URL / PUBLIC_BASE_URL
createdb psh.now # if it doesn't exist
# 3. migrate
bun run migrate
# 4. run the gateway
bun run dev # http://localhost:3000
然后,在你想要保护的项目中:
bun /path/to/psh/cli/psh.ts init
这会创建一个工作区,显示你的 API 密钥,生成配对二维码,并将托管规则块写入 ./agents.md。用手机扫描二维码打开 PWA,轻点配对此设备,即可开始使用。
或在浏览器中创建频道
打开 /setup(例如 http://localhost:3000/setup)。这会创建一个工作区,仅显示一次 API 密钥,并生成配对二维码。配对设备后会进入仪表板,仪表板会立即发送一个测试审批,让你无需使用终端即可确认提醒及完整的批准/拒绝流程。仪表板上的发送测试审批按钮可随时重复此操作,而邀请可让任何已配对设备生成新的配对码。
手动检查
# commands, actions, and questions all use the same call
psh ask "Drop the users table and reseed?" --title "Database reset"
Claude Code 钩子
在 .claude/settings.json 中注册一次钩子:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "psh hook" }]
}
]
}
}
psh hook 会从 stdin 读取 Claude 的 PreToolUse JSON,并将决策约定写入 stdout(permissionDecision: "allow" | "deny")。每个 Bash 命令都会等待你的手机确认。非 Bash 工具则直接通过。将 PSH_SKIP_PATTERN 设为正则表达式,即可自动允许只读命令:
PSH_SKIP_PATTERN='^(ls|cat|git status|rg)\b' psh hook
使用自有密钥连接托管实例
注册后会获得一个工作区和一把密钥。这把密钥是 Agent 的凭据:它能让请求显示在你的账户中,而不是其他人的账户中。
- 获取密钥。 打开托管仪表板,创建工作区并复制密钥。 密钥只会显示一次。同一页面还会生成供手机使用的配对二维码。
- 将密钥放在项目中,而不是 Shell 配置文件中。 在你希望 Agent 操作的项目中安装技能;它会写入
<project>/.psh/keys.json(并在~/.psh/keys.json中写入一条机器记录)。密钥按项目区分是有意为之,这样两个项目可以属于两个不同的工作区,而且密钥文件绝不能纳入版本控制。 - 发起请求。 CLI 和技能的
psh垫片会读取<project>/.psh/keys.json, 并从工作目录开始向上查找。无需设置任何全局配置。 - 从刘海屏或手机上批准。 刘海屏完全不会使用 Agent 密钥:它会配对一台设备(在仪表板中依次选择 Devices、Connect a device),并在
~/.config/psh-notch/device.json中存储该设备专属的令牌,权限模式为 0600。该令牌可以批准请求并读取信息,但不能轮换密钥;你可以按设备撤销令牌。
因此,这两种凭据刻意彼此独立:密钥让 Agent 可以发起请求,设备令牌让人类可以作出回应。在不同机器间复制密钥,转移的是 Agent;复制设备令牌,转移的是审批界面。
托管与自托管
托管版 psh.now 和自托管版都是同一个单进程。唯一能让部署变成“公开”的是 PUBLIC_BASE_URL:
PUBLIC_BASE_URL=https://psh.now
设置后,所有生成的配对链接都会变成
https://psh.now/pair/<code>,可在任何地点通过任何手机打开。
Agent 端点就是相同的源:
curl -s -X POST https://psh.now \
-H "x-psh-key: psh_live_…" \
-H "Content-Type: application/json" \
-d '{"title":"Reinstall deps","message":"rm -rf node_modules && npm install --force"}'
PWA 会根据 location.host 推导 WebSocket URL,因此通过 HTTPS 提供服务时会自动使用 wss://…,无需重新构建。一个实例可为多个相互隔离的工作区/租户提供服务。
最简单的部署方式(无需 Docker 或反向代理)
Bun 就是完整的运行环境。将它指向 Postgres 并运行:
cp .env.example .env # set DATABASE_URL + PUBLIC_BASE_URL
bun src/db/migrate.ts
bun src/index.ts
就这么简单。将其交由 systemd(或 tmux/pm2)运行,即可拥有一个主机节点。
不使用 Caddy 的 HTTPS/WSS
Bun 会自行终止 TLS,无需 Caddy 或 nginx。获取证书(例如从 Let's Encrypt 获取),然后将 psh.now 指向该证书:
export TLS_CERT_FILE=/etc/letsencrypt/live/psh.now/fullchain.pem
export TLS_KEY_FILE=/etc/letsencrypt/live/psh.now/privkey.pem
export PUBLIC_BASE_URL=https://psh.now
bun src/index.ts
服务器会记录 https://…,PWA 会自动使用 wss://…。如果未设置 TLS_CERT_FILE/TLS_KEY_FILE,则会提供纯 HTTP 服务(并在启动时说明)。使用 certbot certonly 并配置部署钩子进行续期,或者使用你已在管理的任意证书。
只有在你已经运行反向代理、想在一个 IP 上托管多个服务,或需要自动续期证书而不改动 psh 时,反向代理才有用。 此类场景可使用
deploy/Caddyfile,但它不是必需的。
阻塞请求与代理超时。
POST /会保持连接打开,直到有人作出回应。Cloudflare 和许多代理会在约 100 秒后切断源站响应(Cloudflare 会返回 524)。请将DEFAULT_TIMEOUT_MS设为低于代理的限制(默认值为 90s),或者直接运行 psh.now/自托管且不经过 CDN。Agent 必须将 524/超时视为未获批准(技能中对此有说明)。
Docker(可选)
如果你偏好使用容器,Dockerfile + docker-compose.yml 会将 Postgres 和网关打包在一起。它们不是必需项,也不是默认方案:
cp .env.example .env
# edit PUBLIC_BASE_URL (or keep http://localhost:3000 for local)
docker compose up -d --build
迁移具有幂等性,因此每次启动时运行都是安全的。
集成:agents.md(或技能)中的一个密钥
整个接入只需一个主机、一个密钥和一条指令。psh init 会将下面的代码块写入 ./agents.md,并填入你的实际主机和密钥(参见 templates/agents-snippet.md):
## Ask before you act
Before any destructive, irreversible, or production action, ask first:
curl -s -X POST https://psh.now \
-H "x-psh-key: psh_live_…" \
-H "Content-Type: application/json" \
-d '{"title":"<short label>","message":"<what you are about to do>"}'
Proceed only if the response is {"approved":true}. Otherwise do not act.
支持技能的运行环境也可以加载 skills/psh-now/SKILL.md 来替代(或同时使用)。它以 Anthropic Agent Skills / Hermes 格式向任何 LLM 说明相同的约定,其中包含 metadata.hermes 代码块,并补充了关于何时询问以及何时不询问的指导。
安装技能(格式与 here.now 的相同):
# if you have npm/npx: one command, so the harness asks for approval once.
# It installs machine-wide and mints in the same run.
npx skills add passy-ai/psh-now-skill -g -y
# no npm? the hosted installer (works in a sandbox too, no credentials needed)
curl -fsSL https://psh.now/install.sh | sh
然后向 Agent 提供主机和密钥(通过环境变量或 agents.md):
export PSH_BASE_URL="https://psh.now"
export PSH_API_KEY="psh_live_..."
服务本身提供技能和安装程序:/skill.md、/install.sh、/llms.txt、/llms-full.txt 和 /.well-known/* 清单。
这就是任意 Agent 所需的全部集成接口:
- 询问:模型遵循
agents.md/ 技能并调用主机。任何可使用 shell 或 HTTP 工具的 LLM 都能做到。 - 拦截:将
psh hook接入运行环境的工具调用前钩子,且无法跳过。此项可选,仅适用于支持它的运行环境。
agents.md中的 API 密钥属于机密。如果要提交该文件,请将其加入 gitignore,或将密钥保存在PSH_API_KEY中并引用该环境变量。
代码块的工作原理
- Agent → 网关。 Agent 使用
x-psh-key请求头,将{ title, message, kind?, cwd?, agent? }发送到POST /(或/api/v1/hook)。(command仍作为message的旧版别名接受。) - 风险扫描。 系统会根据规则集扫描标题和消息(
rm -rf、--force、sudo、curl | sh、DROP TABLE、强制推送等),并按low → critical进行分级,同时给出易于理解的原因。 - 等待。 请求的 promise 会暂存在进程内的等待者注册表中,并设置超时。HTTP 连接保持打开状态,终端会冻结。**异步选项:**发送
wait:false后,调用会立即返回pollUrl,因此不会阻塞,也不会因代理/CDN 超时而中断。使用GET /api/v1/prompts/:id?wait=30进行轮询。 - 提醒。 提示会广播给该租户所有已配对的设备。
- 决策。 手机通过 WebSocket 发送
{ type: "decision", promptId, approved }(或在备用情况下使用POST /api/v1/prompts/:id/decision)。决策会以原子方式持久化(pending → approved/denied),通过NOTIFY psh_decisions扇出,然后解除等待者的等待。 - 解除阻塞。 Agent 会收到
{ approved: true|false }并采取相应操作。
超时或 Agent 连接断开时会自动拒绝(status: "expired"),因此即使手机丢失,也不会让构建永远卡住。对于等待答复的时间窗口已过期的待处理提示,系统会将其清理为 expired(异步请求没有等待者,因此无需处理超时)。
HTTP API
| 方法 | 路径 | 身份验证 | 用途 |
|---|---|---|---|
POST | / · /api/v1/hook | x-psh-key | 审批请求(阻塞,或使用 wait:false) |
POST | /mcp | x-psh-key | MCP:一个工具 psh_ask(可流式传输的 HTTP,JSON) |
GET | /.well-known/mcp.json | - | 面向客户端和扫描器的 MCP 描述文件 |
POST | /api/v1/channels | - | 创建工作区(使用 pair:true 创建配对链接) |
POST | /api/v1/claims | key / device | 为匿名工作区申请认领码 |
POST | /api/v1/claims/:id/verify | - | 验证代码;使工作区永久有效 |
POST | /api/v1/pairing | key / device | 生成配对链接/二维码 |
GET | /api/v1/qr | - | 将任意字符串渲染为 SVG 二维码 |
GET | /api/v1/pairing/:token | - | 验证配对令牌 |
POST | /api/v1/pairing/:token/claim | - | 将令牌兑换为设备令牌 |
GET | /api/v1/me | x-psh-key | 工作区概览 |
GET | /api/v1/prompts | key / device | 列出提示词(?status=、?limit=) |
GET | /api/v1/prompts/:id | key / device | 获取提示词;?wait=<s> 会长轮询 |
POST | /api/v1/prompts/:id/cancel | key / device | 取消待处理的请求 |
POST | /api/v1/connections | key / device | 注册连接(Agent/项目) |
GET | /api/v1/connections | key / device | 列出连接(一个账户 → 多个连接) |
GET | /api/v1/connections/:id | key / device | 一个连接及其请求 |
DELETE | /api/v1/connections/:id | key / device | 删除连接 |
POST | /api/v1/test | key / device | 发送非阻塞测试审批 |
POST | /api/v1/prompts/:id/decision | key / device | 通过 HTTP 决策(WS 回退) |
GET | /api/v1/devices | key / device | 列出已配对设备 |
DELETE | /api/v1/devices/:id | key / device | 撤销设备 |
GET | /api/v1/keys | key / device | 列出 Agent 密钥 |
POST | /api/v1/keys | key / device | 生成可恢复的 Agent 密钥 |
GET | /api/v1/keys/:id/reveal | key / device | 向操作员显示密钥的原始值 |
DELETE | /api/v1/keys/:id | key / device | 撤销 Agent 密钥 |
GET | /api/v1/health | - | 健康检查 |
WS | /ws?token=<deviceToken> | device token | 实时提示词流 + 决策 |
交互式文档见 /docs(Swagger)。
MCP
HTTP 调用是契约;MCP 则是为无法安装技能的客户端(托管式构建器、连接器 UI、仅支持该协议的应用)提供的第二个入口。一个端点、一个工具、无需 OAuth,也没有服务端主动发起的流——这是围绕同一处理程序构建的轻量 JSON-RPC 封装,因此两个入口的计量、过期时间和决策路径不会出现偏差。
claude mcp add --transport http psh https://psh.now/mcp --header "x-psh-key: psh_agent_..."
{ "mcpServers": { "psh": { "url": "https://psh.now/mcp",
"headers": { "x-psh-key": "psh_agent_..." } } } }
psh_ask 接受 {title?, message, timeoutSec?},会阻塞,并返回
{"approved":bool,"status":"approved|denied|answered|expired","answer":string|null,"promptId":"..."}。
只有 approved: true 才表示同意;denied、expired 和错误均表示不得采取行动。与技能一样,MCP 是协作式的:它无法阻止一个从未调用它的 Agent。真正把关的是钩子。
协议详情、错误代码以及采用更精简接口的原因:docs/mcp.md。
WebSocket 帧
服务端 → 手机:
{ "type": "hello", "workspace": {…}, "device": {…}, "pending": [...] }
{ "type": "prompt.created", "prompt": { "id": "pr8x1k…", "title": "Reinstall deps", "message": "rm -rf node_modules", "kind": "command", "risk": "critical", "riskReasons": [...] } }
{ "type": "prompt.decided", "promptId": "…", "approved": true }
手机 → 服务端:
{ "type": "decision", "promptId": "pr8x1k…", "approved": true, "reason": "ok" }
{ "type": "ping" }
安全模型
- API 密钥仅显示一次,且只以 SHA-256 哈希形式存储。
- 设备令牌为随机生成,以哈希形式存储,并可按设备撤销。
- 配对令牌只能使用一次,且会过期(默认 10 分钟)。
- 短 ID。 所有公开标识符均采用 base62,长度 ≤ 32 个字符,因此所有 URL 都保持 YouTube 短链接长度(
/pair/UpDbWPKjQjoAqVtLdehnwQ)。 - 仅在传输过程中使用短期凭证;配对后,绝不会向 Agent 发送机密信息,也不会将其嵌入 URL。
- 每项决策都会记录在只追加的
audit_log中。
多节点 / 扩展
阻塞中的 Promise 存在于接收 Agent 请求的节点上;手机可能连接到另一个节点。NOTIFY psh_decisions 会将每项决策广播给所有节点,每个节点都会处理与之匹配的本地等待项。无需粘性会话,也不需要 Redis。Postgres 是唯一的协调原语。
项目布局
src/
index.ts Elysia app + graceful shutdown
env.ts Validated configuration
db/
schema.ts Drizzle tables (tenants, devices, prompts, …)
index.ts postgres.js pool + Drizzle client
migrate.ts Migration runner
lib/
ids.ts Short base62 ID generator
crypto.ts API key generation, SHA-256, header parsing
risk.ts Command risk scanner
waiters.ts In-process blocked-request registry
realtime.ts WebSocket per-tenant hub
pubsub.ts Postgres LISTEN/NOTIFY fan-out
tenant-auth.ts Request authentication
http.ts Typed HTTP errors
services/ Business logic (tenants, devices, prompts, decisions)
routes/ auth · hook · prompts · ws
public/ PWA (index/pair HTML, app.js, sw.js, manifest)
cli/psh.ts init · pair · ask · hook · status · doctor
tests/e2e.ts Full approval-loop integration test
drizzle/ Generated SQL migrations
测试
bun tests/e2e.ts # boots the gateway, pairs a fake phone, exercises approve/deny/timeout
bunx tsc --noEmit # type check
CLI
psh init Create a workspace + pairing QR
psh pair Mint a new pairing link
psh ask "cmd" Block until a human approves/denies [--timeout <seconds>]
psh hook Agent hook (reads JSON on stdin)
psh status Workspace + pending requests
psh doctor Connectivity + credential check
许可证
MIT,版权所有 (c) 2026 Absolum。参见 LICENSE。托管实例 psh.now 是一项服务,不属于本许可证的范围。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。