psh.now について
psh.nowは、AIエージェントの要求を一時停止し、人が承認、拒否、または返信するための承認サービスです。シェルコマンドまたはHTTPを使えるエージェントに対応し、セルフホストも可能です。
主な機能
- 01エージェント要求の人間による承認
- 02スマートフォンからの承認・拒否
- 03自由記述での要求への返信
- 04CLIとHTTP API
- 05Claude Codeのツール実行前フック
- 06セルフホストでの運用
活用シーン
- 01破壊的なコマンドの前に承認を求める
- 02スマートフォンから本番操作を確認する
- 03エージェントの実行中に質問へ回答する
- 04Claude 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 エージェントに人間の承認を:ホスト1つ、キー1つ、ブール値1つ。

判断が必要なエージェントは、短いタイトルとメッセージを psh.now に送信して待機します。 1つのページで承認または拒否をタップするか、自分の言葉で回答すると、エージェントは結果を受け取って処理を続けます。誰も見ていないターミナルでエージェントが停止してしまう問題を解決するために作られました。
- シェルコマンドを実行できる、または HTTP リクエストを送信できるエージェントなら、どれでも使えます。SDK も MCP も不要です。
- 接続できないサンドボックスも含め、あらゆるエージェントとマシンで使える1つのページ。
- セルフホスト可能:MIT ライセンス、コンテナ2つ、環境変数3つ。
┌────────────┐ blocking POST / ┌────────────────┐ WebSocket ┌─────────┐
│ AI agent │ ───────────────────────► │ psh.now gateway │ ─────────────► │ phone │
│ (terminal) │ │ (Elysia + │ │ (PWA) │
│ │ ◄─────────────────────── │ Postgres) │ ◄───────────── │ │
└────────────┘ { approved: true } └────────────────┘ decision └─────────┘
リクエストはタイトルとメッセージで構成され、シェルコマンドだけではありません。実行するコマンド、実行するアクション、または回答する質問を指定できます。
次に読む
| 目的 | 読む |
|---|---|
| インストールして最初のリクエストに回答する | クイックスタート |
| 自分のサーバーで実行する | docs/self-hosting.md または任意のインスタンスの /self-hosting |
| 処理の流れを最初から最後まで理解する | docs/how-it-works.md |
| API を直接呼び出す | HTTP API |
| エージェントにプロンプトを貼り付ける | 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 キーが表示され、ペアリング用 QR コードが生成され、./agents.md に管理対象のルールブロックが書き込まれます。スマートフォンで QR コードをスキャンして PWA を開き、このデバイスをペアリングをタップすると、すぐに使えます。
またはブラウザーでチャンネルを作成する
/setup(例:http://localhost:3000/setup)を開きます。ワークスペースが作成され、API キーが一度だけ表示され、ペアリング用 QR コードが生成されます。デバイスをペアリングするとダッシュボードが開き、すぐに承認テストが送信されます。これにより、ターミナルを使わずに通知と承認・拒否の一連の流れを確認できます。ダッシュボードの承認テストを送信ボタンでいつでも再実行でき、招待から、すでにペアリング済みの任意のデバイスで新しいペアリングコードを発行できます。
手動で確認する
# 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 は Claude の PreToolUse JSON を標準入力から読み込み、判断の契約を標準出力に書き込みます(permissionDecision: "allow" | "deny")。すべての Bash コマンドはスマートフォンでの承認を待ちます。Bash 以外のツールはそのまま通過します。読み取り専用コマンドを自動的に許可するには、PSH_SKIP_PATTERN に正規表現を設定します。
PSH_SKIP_PATTERN='^(ls|cat|git status|rg)\b' psh hook
独自のキーを使ってホスト型インスタンスを利用する
サインアップすると、ワークスペースとキーが1つ付与されます。このキーはエージェントの認証情報です。これにより、質問が他の人ではなく、あなたのアカウントに表示されます。
- キーを取得します。 ホスト型ダッシュボードを開き、ワークスペースを作成して、キーをコピーします。 キーは一度だけ表示されます。同じページに、スマートフォン用のペアリングQRコードも表示されます。
- シェルのプロファイルではなく、プロジェクトに設定します。 エージェントに作業させたいプロジェクト内にスキルをインストールします。これにより、
<project>/.psh/keys.json(およびマシンの記録ファイル~/.psh/keys.json)が作成されます。キーは意図的にプロジェクトごとに分けられているため、2つのプロジェクトを別々のワークスペースに所属させることができ、このファイルをバージョン管理に含める必要もありません。 - 質問します。
<project>/.psh/keys.jsonは、CLIとスキルのpshshimが読み込むファイルです。作業ディレクトリから上の階層を検索します。グローバルに設定する必要はありません。 - ノッチまたはスマートフォンから承認します。 ノッチではエージェントキーを一切使用しません。デバイスをペアリングし(ダッシュボードの「Devices」から「Connect a device」を選択)、デバイスごとのトークンを
~/.config/psh-notch/device.jsonにパーミッションモード0600で保存します。このトークンでは承認と読み取りができますが、キーのローテーションはできません。また、デバイスごとに取り消せます。
このように、2つの認証情報は意図的に分けられています。キーでエージェントが質問し、デバイストークンで人間が回答します。キーをマシン間でコピーするとエージェントが移り、デバイストークンをコピーすると承認画面が移ります。
ホスティングとセルフホスティング
ホスト型のpsh.nowもセルフホスト版も、同じ単一プロセスです。デプロイを「公開」にするのは PUBLIC_BASE_URL だけです。
PUBLIC_BASE_URL=https://psh.now
これを設定すると、生成されるすべてのペアリングリンクが
https://psh.now/pair/<code> になり、どこにいても、どのスマートフォンからでも開けるようになります。
エージェントのエンドポイントも同じオリジンです。
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://… が使われます。1つのインスタンスで、互いに隔離された複数のワークスペース/テナントを提供できます。
最もシンプルなデプロイ(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 を使用するか、すでに管理している証明書を使用してください。
リバースプロキシが役立つのは、すでに運用している場合、1つのIPアドレスで複数のサービスをホストしたい場合、またはpshに手を加えずに証明書を自動更新したい場合だけです。
deploy/Caddyfileはその場合に備えて用意されていますが、必須ではありません。
ブロッキング処理とプロキシのタイムアウト。
POST /は人間が回答するまで接続を維持します。Cloudflareや多くのプロキシでは、オリジンからの応答が約100秒で打ち切られます(Cloudflareは524を返します)。DEFAULT_TIMEOUT_MSをプロキシの制限時間(デフォルトは90秒)より短くするか、psh.nowを直接実行するか、CDNを介さずにセルフホストしてください。エージェントは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(またはスキル)にキーを設定する
連携に必要なのは、ホスト、キー、1つの指示だけです。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 形式で、metadata.hermes ブロックを含み、同じ契約をあらゆる LLM に教えます。また、いつ確認を求めるべきか、いつ求めるべきでないかについてもガイダンスを追加します。
スキルをインストールします(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
次に、エージェントにホストとキーを渡します(環境変数または 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/* マニフェストです。
任意のエージェントとの連携に必要なものは、これだけです。
- 確認:モデルは
agents.md/ スキルに従い、ホストを呼び出します。シェルまたは HTTP ツールにアクセスできる LLM なら、どれでも実行できます。 - ゲート:
psh hookをハーネスのツール実行前フックに接続します。このフックはスキップできません。オプションであり、ハーネスが対応している場合にのみ利用できます。
agents.md内の API キーは秘密情報です。そのファイルをコミットする場合は gitignore に追加するか、キーをPSH_API_KEYに格納して、代わりに環境変数を参照してください。
ブロックの仕組み
- エージェント → ゲートウェイ。 エージェントは
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経由で配信され、待機中の処理が再開されます。 - 再開。 エージェントは
{ approved: true|false }を受け取り、処理を実行します。
タイムアウトやエージェント接続の切断が発生すると、自動的に拒否されます(status: "expired")。そのため、スマートフォンが失われても、ビルドが永久に停止することはありません。回答期限を過ぎた保留中のプロンプトは expired に変更されます(非同期リクエストにはタイムアウト対象の待機処理がありません)。
HTTP API
| メソッド | パス | 認証 | 用途 |
|---|---|---|---|
POST | / · /api/v1/hook | x-psh-key | 承認リクエスト(ブロッキング、または wait:false) |
POST | /mcp | x-psh-key | MCP:1 つのツール、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 | ペアリングリンク/QR コードを作成 |
GET | /api/v1/qr | - | 任意の文字列を SVG QR コードとして描画 |
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 | 接続を登録(エージェント/プロジェクト) |
GET | /api/v1/connections | key / device | 接続の一覧(1 つのアカウント → 複数の接続) |
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 | エージェントキーの一覧 |
POST | /api/v1/keys | key / device | 復元可能なエージェントキーを作成 |
GET | /api/v1/keys/:id/reveal | key / device | オペレーターにキーの生の値を表示 |
DELETE | /api/v1/keys/:id | key / device | エージェントキーを失効 |
GET | /api/v1/health | - | ヘルスチェック |
WS | /ws?token=<deviceToken> | device token | ライブプロンプトストリームと決定 |
インタラクティブなドキュメントは /docs にあります(Swagger)。
MCP
HTTP 呼び出しが契約です。MCP は、スキルをインストールできないクライアント(ホステッドビルダー、コネクター UI、プロトコルだけを扱うアプリ)向けの第 2 の窓口です。エンドポイントは 1 つ、ツールは 1 つ、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 はスキルと同様に協調的に動作するため、呼び出さないエージェントを止めることはできません。ゲートの役割を担うのはフックです。
プロトコルの詳細、エラーコード、インターフェースを小さくした理由については、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 ハッシュとしてのみ保存されます。
- デバイストークンはランダムに生成され、ハッシュ化して保存されます。デバイスごとに取り消し可能です。
- ペアリングトークンは 1 回限り使用でき、有効期限があります(デフォルトは 10 分)。
- 短い ID。 すべての公開識別子は base62 で、長さは最大 32 文字です。そのため、すべての URL は YouTube の短縮 URL と同じくらい短くなります(
/pair/UpDbWPKjQjoAqVtLdehnwQ)。 - 有効期間の短い認証情報のみを通信経路で送信します。ペアリング後、エージェントに秘密情報が送信されたり、URL に埋め込まれたりすることはありません。
- すべての判断は追記専用の
audit_logに記録されます。
マルチノード/スケーリング
ブロックされた Promise はエージェントのリクエストを受け付けたノード上に保持されますが、電話は別のノードに接続されている場合があります。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、Copyright (c) 2026 Absolum。LICENSE をご覧ください。psh.now のホステッドインスタンスはサービスであり、このライセンスの対象ではありません。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。