本文へ移動
cccskills
無料GitHub で公開日本語紹介

pinggy-tunnel

ローカルで動く開発サーバーやWebhook受信先を、SSH経由でインターネットに公開するスキル。専用ソフトの追加なしで、共有用URLの取得やアクセス制限を行えます。

原文原文の説明を見る

Zero-install localhost tunnels over SSH via Pinggy.

インストール方法を見る

こんなときに便利

  • 実行中の開発サーバーを共有したいとき
  • Webhookをローカルで受信したいとき
  • ローカルのHTTP型MCPサーバーへの外部接続
  • ローカルモデルの接続先を共有したいとき

日本語での紹介

できること

ローカルで動くサービスをPinggy経由で公開し、外部からアクセスできるURLを取得します。SSHで通信の通り道を作り、開発サーバーの共有やWebhookの受信に使えます。公開URLの動作確認と接続の終了も扱い、パスワード、トークン、接続元IPによるアクセス制限を設定できます。

こんなときに便利

実行中のアプリを離れた相手に見せたいときや、外部サービスから届く通知を手元の環境で受け取りたいときに向いています。HTTPで通信するMCPサーバーや、Ollamaなどのローカルモデルの接続先を共有する用途もあります。

使い方の例

  • 「ポート3000の開発サーバーを、パスワード付きで共有して」
  • 「ローカルのWebhook受信先に公開URLを付けて」
  • 「HTTPで動くMCPサーバーを、トークン付きで外部から接続できるようにして」

注意点

sshと、起動済みのローカルサービスが必要です。無料枠は登録不要ですが、接続は60分で終了し、再接続時にURLが変わります。同じ接続元IPでの無料接続は1つに制限されます。有料機能にはPINGGY_TOKENを使います。非公開のサービスにはアクセス制限を設定してください。stdio方式のMCPサーバーは対象外です。

この紹介文は、公開されている SKILL.md をもとに AI(Claude Haiku)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(1)

  • SKILL.md13.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Pinggy Tunnel Skill

Expose a local service (dev server, webhook receiver, MCP endpoint, demo) to the public internet using a Pinggy SSH reverse tunnel. No daemon to install — the user's stock SSH client connects to a.pinggy.io:443 and Pinggy hands back a public HTTP/HTTPS URL.

Free tier: 60-minute tunnels, random subdomain, no signup. Pro tier ($3/mo) is an opt-in with a token.

When to Use

  • User asks to "expose this locally", "share my dev server", "make this URL public", "tunnel port N", "get a public URL for a webhook"
  • Need to receive a webhook callback during a local task (Stripe, GitHub, Discord, AgentMail)
  • Sharing a one-off HTTP demo (MCP server, Ollama/vLLM endpoint, dashboard) with a remote party
  • The host has SSH but no cloudflared / ngrok binary, and installing one would be overkill

If the host already has cloudflared configured, prefer the cloudflared-quick-tunnel skill — Cloudflare quick tunnels don't expire after 60 minutes.

Prerequisites

  • ssh on PATH (ssh -V). Default on Linux, macOS, and Windows 10+. No other install.
  • A local service listening on 127.0.0.1:<port> before the tunnel starts. Pinggy will return URLs but they'll 502 until the local origin is up.

Optional:

  • PINGGY_TOKEN env var for paid Pro features (persistent subdomain, custom domain, multiple tunnels, no 60-minute cap). Free tier needs no credentials.

Quick Reference

# Plain HTTP/HTTPS tunnel for port 8000 (free tier)
ssh -p 443 -o StrictHostKeyChecking=no -o ServerAliveInterval=30 \
    -R0:localhost:8000 free@a.pinggy.io

# TCP tunnel (databases, raw SSH, etc.)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:5432 tcp@a.pinggy.io

# TLS tunnel (Pinggy can't decrypt — bring your own certs at origin)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:443 tls@a.pinggy.io

# Basic auth gate (b:user:pass)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 \
    "b:admin:secret+free@a.pinggy.io"

# Bearer token gate (k:token)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 \
    "k:mysecrettoken+free@a.pinggy.io"

# IP whitelist (w:CIDR)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 \
    "w:203.0.113.0/24+free@a.pinggy.io"

# Enable CORS + force HTTPS redirect
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 \
    "co+x:https+free@a.pinggy.io"

# Pro tier (persistent URL, no 60-min cap)
ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 "$PINGGY_TOKEN+a.pinggy.io"

Procedure — Start a Tunnel and Get the URL

The model SHOULD use the terminal tool. The tunnel must stay alive for the duration of the share, so run it as a background process and parse the public URL from stdout.

1. Confirm a local origin is up

curl -sI http://127.0.0.1:8000/ | head -1
# expect HTTP/1.x 200 (or any non-connection-refused response)

If nothing is listening yet, start it first (e.g. python -m http.server 8000 --bind 127.0.0.1). Pinggy will happily return a URL pointed at nothing — the user will see 502 until the origin comes up.

2. Launch the tunnel as a background process

Use terminal(background=True) and capture output to a logfile (Pinggy prints the URLs on stdout, then keeps the connection open):

LOG=~/.hermes/cache/scratch/pinggy-8000.log
nohup ssh -p 443 \
    -o StrictHostKeyChecking=no \
    -o UserKnownHostsFile=/dev/null \
    -o ServerAliveInterval=30 \
    -o ServerAliveCountMax=3 \
    -R0:localhost:8000 free@a.pinggy.io \
    > "$LOG" 2>&1 &
echo $! > ~/.hermes/cache/scratch/pinggy-8000.pid

StrictHostKeyChecking=no + UserKnownHostsFile=/dev/null skips the first-run host-key prompt. ServerAliveInterval=30 keeps the SSH session from getting torn down by an idle NAT.

3. Parse the URL out of the log

sleep 4
grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' ~/.hermes/cache/scratch/pinggy-8000.log | head -1

Expected output looks like:

You are not authenticated.
Your tunnel will expire in 60 minutes.
http://yqycl-98-162-69-48.a.free.pinggy.link
https://yqycl-98-162-69-48.a.free.pinggy.link

Hand the https://...pinggy.link URL to the user.

4. Verify

curl -sI https://<the-url>/ | head -3
# expect 200/302/whatever the local origin actually returns

If you get 502 Bad Gateway, the SSH session is up but the local origin isn't listening — fix step 1 first.

5. Teardown

kill "$(cat ~/.hermes/cache/scratch/pinggy-8000.pid)"
# or, if the pid file got lost:
pkill -f 'ssh -p 443 .* free@a\.pinggy\.io'

If you have a session_id from terminal(background=True), prefer process(action='kill', session_id=...).

Access Control via Username Keywords

Pinggy stacks control flags into the SSH username separated by +. Always quote the whole user@host argument when it contains a +:

KeywordEffect
b:user:passHTTP Basic auth gate
k:tokenBearer-token header gate (Authorization: Bearer <token>)
w:CIDRIP whitelist (single IP or CIDR, repeatable)
coAdd Access-Control-Allow-Origin: * (CORS)
x:httpsForce HTTPS — auto-redirect HTTP to HTTPS
a:Name:ValueAdd request header
u:Name:ValueUpdate request header
r:NameRemove request header
qrPrint a QR code of the URL to stdout (handy for mobile sharing)

Combine freely: "b:admin:secret+co+x:https+free@a.pinggy.io".

Web Debugger (optional)

Pinggy can mirror the inbound traffic to localhost:4300 for inspection. Add a local forward to the SSH command:

ssh -p 443 -L4300:localhost:4300 -R0:localhost:8000 free@a.pinggy.io

Then open http://localhost:4300 in a browser to see live request/response pairs.

Pitfalls

  • 60-minute hard cap on the free tier. The SSH session terminates at the 60-minute mark; the URL goes dead. For longer shares, either use PINGGY_TOKEN (Pro) or auto-restart with a shell loop (note that the URL changes on every restart for free-tier).
  • Free-tier URL is random and changes on restart. Don't bookmark it, don't paste it into a config file. Re-parse from the log each time.
  • Concurrent free tunnels are limited to one per source IP. Starting a second tunnel from the same machine usually kills the first. Pro tier lifts this.
  • + in usernames must be quoted. Bare ssh ... b:admin:secret+free@a.pinggy.io works in bash but breaks under shells that treat + specially or when assembled programmatically. Always wrap in double quotes.
  • Don't tunnel anything sensitive without an access-control flag. A bare HTTP tunnel is reachable by anyone with the URL. Use b:, k:, or w: for non-public services.
  • process(action='log') may miss SSH banner output. Pinggy prints the URLs and then the SSH session goes interactive. Always redirect to a logfile and grep the file directly — same pattern as cloudflared-quick-tunnel.
  • Host-key prompt on first run. Default OpenSSH config asks the user to accept Pinggy's host key. Always pass -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null for unattended runs.
  • TCP and TLS tunnels return a <subdomain>.a.pinggy.online:<port> pair, not an https URL. Parse with a different regex (tcp:// and a port). Don't assume every Pinggy tunnel is HTTP.
  • Pro mode requires the token as the username, not a flag. Use "$PINGGY_TOKEN+a.pinggy.io" (no free@). With a token you can also add :persistent for a stable subdomain — see pinggy.io/docs/.

Recipes

Composite patterns combining a local origin with a Pinggy tunnel. Each recipe is self-contained — start the origin, start the tunnel, parse the URL, hand it back to the user.

Recipe 1 — Receive a webhook callback

Use this when an external service (Stripe, GitHub, Discord, AgentMail, etc.) needs to POST to a publicly reachable URL during a local task.

# 1. Tiny capturing server: every request gets appended to ~/.hermes/cache/scratch/webhook-hits.log
cat >~/.hermes/cache/scratch/webhook-server.py <<'PY'
import http.server, json, datetime, pathlib
LOG = pathlib.Path("~/.hermes/cache/scratch/webhook-hits.log").expanduser()
class H(http.server.BaseHTTPRequestHandler):
    def _capture(self):
        n = int(self.headers.get("content-length") or 0)
        body = self.rfile.read(n).decode("utf-8", "replace") if n else ""
        rec = {"t": datetime.datetime.utcnow().isoformat(), "path": self.path,
               "method": self.command, "headers": dict(self.headers), "body": body}
        with LOG.open("a") as f: f.write(json.dumps(rec) + "\n")
        self.send_response(200); self.send_header("content-type","application/json")
        self.end_headers(); self.wfile.write(b'{"ok":true}\n')
    def do_GET(self): self._capture()
    def do_POST(self): self._capture()
    def log_message(self,*a,**k): pass
http.server.HTTPServer(("127.0.0.1", 18080), H).serve_forever()
PY
nohup python ~/.hermes/cache/scratch/webhook-server.py >~/.hermes/cache/scratch/webhook-server.log 2>&1 &
echo $! >~/.hermes/cache/scratch/webhook-server.pid

# 2. Tunnel — bearer-token-gate so randos can't pollute the capture log
nohup ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
    -o ServerAliveInterval=30 \
    -R0:localhost:18080 "k:$(openssl rand -hex 12)+free@a.pinggy.io" \
    >~/.hermes/cache/scratch/webhook-pinggy.log 2>&1 &
echo $! >~/.hermes/cache/scratch/webhook-pinggy.pid
sleep 5
URL=$(grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' ~/.hermes/cache/scratch/webhook-pinggy.log | head -1)
echo "Webhook URL: $URL"

# 3. While the agent works, watch hits land
tail -f ~/.hermes/cache/scratch/webhook-hits.log

Hand $URL to the service that needs to call you. Teardown: kill $(cat ~/.hermes/cache/scratch/webhook-server.pid) $(cat ~/.hermes/cache/scratch/webhook-pinggy.pid).

Recipe 2 — Expose an MCP server over HTTP/SSE

Use when a remote MCP client (Claude Desktop on another machine, a teammate's editor, etc.) needs to reach an MCP server running on the local box. Only works for MCP servers that speak HTTP transport — stdio-mode servers can't be tunneled.

# 1. Start the MCP server in HTTP mode (example: a FastMCP server on port 8765)
nohup python my_mcp_server.py --transport http --port 8765 \
    >~/.hermes/cache/scratch/mcp-server.log 2>&1 &
echo $! >~/.hermes/cache/scratch/mcp-server.pid

# 2. Tunnel with a bearer token — MCP traffic should not be open to the internet
TOKEN=$(openssl rand -hex 16)
nohup ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
    -o ServerAliveInterval=30 \
    -R0:localhost:8765 "k:$TOKEN+free@a.pinggy.io" \
    >~/.hermes/cache/scratch/mcp-pinggy.log 2>&1 &
echo $! >~/.hermes/cache/scratch/mcp-pinggy.pid
sleep 5
URL=$(grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' ~/.hermes/cache/scratch/mcp-pinggy.log | head -1)
echo "MCP URL: $URL"
echo "Bearer token: $TOKEN"

The remote client connects to $URL with Authorization: Bearer $TOKEN. Hermes' own native MCP client config: {"transport": "http", "url": "<URL>", "headers": {"Authorization": "Bearer <TOKEN>"}}.

Recipe 3 — Expose a local LLM endpoint (Ollama / vLLM / llama.cpp)

Share a local model with a remote caller (another agent, a phone, a teammate). Ollama listens on :11434, vLLM and llama.cpp typically on :8000.

# Pre-req: the model server is already running on 127.0.0.1:11434 (Ollama default)
TOKEN=$(openssl rand -hex 16)
nohup ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
    -o ServerAliveInterval=30 \
    -R0:localhost:11434 "k:$TOKEN+co+free@a.pinggy.io" \
    >~/.hermes/cache/scratch/llm-pinggy.log 2>&1 &
echo $! >~/.hermes/cache/scratch/llm-pinggy.pid
sleep 5
URL=$(grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' ~/.hermes/cache/scratch/llm-pinggy.log | head -1)
echo "Endpoint: $URL"
echo "Token:    $TOKEN"

# Verify
curl -s "$URL/api/tags" -H "Authorization: Bearer $TOKEN" | head

co enables CORS so a browser caller can hit the endpoint. Drop co for backend-only callers. For an OpenAI-compatible vLLM/llama.cpp endpoint, callers use base URL $URL/v1 with Authorization: Bearer $TOKEN — but note Pinggy strips/replaces nothing in the body, so the model server itself sees Pinggy's token; the local server should be configured to ignore auth (it's already on 127.0.0.1) and let Pinggy do the gating.

Recipe 4 — Share a dev server with a one-shot password

The fastest "let a teammate poke at my running app" pattern. Random password, prints once, dies when you Ctrl-C.

PASS=$(openssl rand -base64 12 | tr -d '+/=' | head -c 12)
echo "Dev server password: $PASS"
ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
    -o ServerAliveInterval=30 \
    -R0:localhost:3000 "b:dev:$PASS+co+x:https+free@a.pinggy.io"
# URL prints to the terminal. Share URL + password. Ctrl-C to tear down.

b:dev:$PASS gates the URL with HTTP Basic auth. x:https forces TLS. co adds CORS for SPA frontends.

Verification

# End-to-end: spin up a trivial origin, tunnel it, hit it, tear down
python -m http.server 18000 --bind 127.0.0.1 >~/.hermes/cache/scratch/origin.log 2>&1 &
ORIGIN_PID=$!

nohup ssh -p 443 \
    -o StrictHostKeyChecking=no \
    -o UserKnownHostsFile=/dev/null \
    -R0:localhost:18000 free@a.pinggy.io >~/.hermes/cache/scratch/pinggy-verify.log 2>&1 &
SSH_PID=$!

sleep 5
URL=$(grep -oE 'https://[a-z0-9-]+\.[a-z]+\.pinggy\.link' ~/.hermes/cache/scratch/pinggy-verify.log | head -1)
echo "URL: $URL"
curl -sI "$URL/" | head -1

kill "$SSH_PID" "$ORIGIN_PID"

Expected: a pinggy.link URL and HTTP/2 200 on the curl head.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

1password

無料日本語概要

1Passwordに保存したパスワードやAPIキーを、コマンド実行や設定テンプレートで使うためのスキル。CLIの導入、認証、秘密情報の取得・受け渡しを案内します。

  • 1Password CLIの導入と設定
  • 保存済みAPIキーでコマンド実行
  • 設定テンプレートへの秘密情報の埋め込み
NousResearch/hermes-agent25.3万2026年10月12日 更新

3-statement-model

無料日本語概要

損益計算書・貸借対照表・キャッシュフロー計算書を数式で連動させるExcelモデルを作ります。実績と予測の入力、前提を変えた比較、財務三表の整合性確認を段階的に進めます。

  • 財務テンプレートへの実績入力
  • 財務三表が連動する予測の作成
  • 基本・上振れ・下振れシナリオの比較
NousResearch/hermes-agent25.3万2026年10月12日 更新

Run PyTorch training across GPUs with minimal changes.

日本語の概要は準備中です。原文の説明を表示しています。

NousResearch/hermes-agent25.3万2026年10月12日 更新

Set up Actual Computer (actual.inc) inference in Hermes.

日本語の概要は準備中です。原文の説明を表示しています。

NousResearch/hermes-agent25.3万2026年10月12日 更新

adversarial-ux-test

無料日本語概要

技術が苦手で不満を抱きやすい利用者になりきってアプリを試し、操作の分かりにくさや離脱の原因を発見。実際の改善課題と個人的な不満を分け、修正案をまとめます。

  • 公開やデモ前に使いやすさを確認したいとき
  • 新規ユーザーの体験を点検したいとき
  • 登録・課金でのつまずきを探したいとき
NousResearch/hermes-agent25.3万2026年10月12日 更新

agent-merge-conflict-arbiter

無料日本語概要

2つのエージェントが別々に加えた変更のGit競合を、双方の差分と意図から中立的に解決。競合箇所ごとの判断理由を記録し、ビルドやテストで統合結果を確認します。

  • 2つのエージェントのブランチ競合を解消
  • 並行したworktreeの変更を統合
  • 競合する設計判断の理由を確認したいとき
NousResearch/hermes-agent25.3万2026年10月12日 更新

NousResearch のスキルをすべて見る

このスキルの問題を報告する