View currently active local connections to confirm the rule, policy group, and real egress hit by a specified process. Does not accept SSH parameters; for remote machines, first log in via Tailscale SSH, then run the same read-only procedure on the target machine.
User-supplied arguments: $ARGUMENTS. When there are no arguments, list all active connections. When diagnosing Gemini, ChatGPT, or browser traffic, first get the actual metadata.processwithout filters, then use the observed process name; do not assume they belong to claude。
Obtain credentials
Read the Clash Verge configuration, but do not print the secret:
SECRET=$(grep '^secret:' "$HOME/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml" 2>/dev/null | awk '{print $2}')
[ -z "$SECRET" ] && SECRET=$(grep '^secret:' "$HOME/.config/clash/config.yaml" 2>/dev/null | awk '{print $2}')
[ -n "$SECRET" ] && echo "API secret: configured" || echo "API secret: not configured"
Query and fallback
Only adopt that endpoint if the request succeeds./tmp When the socket exists but is invalid, continue trying /var/tmp, and finally try the configured HTTP controller. If all endpoints fail, report an explicit error and do not return empty data.
request_connections() {
for socket_path in \
/tmp/verge/verge-mihomo.sock \
/var/tmp/verge/verge-mihomo.sock
do
[ -S "$socket_path" ] || continue
if curl --fail --silent --show-error \
--unix-socket "$socket_path" \
"http://localhost/connections" \
-H "Authorization: Bearer $SECRET"
then
return 0
fi
done
controller=$(grep '^external-controller:' "$HOME/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/clash-verge.yaml" 2>/dev/null | awk '{print $2}' | tr -d "'\"")
[ -n "$controller" ] || controller="127.0.0.1:9090"
curl --fail --silent --show-error \
"http://$controller/connections" \
-H "Authorization: Bearer $SECRET"
}
if ! DATA=$(request_connections); then
echo "无法从 Mihomo Unix socket 或 HTTP controller 获取连接数据" >&2
exit 1
fi
if ! printf '%s' "$DATA" | python3 -c 'import json, sys; value=json.load(sys.stdin); assert isinstance(value.get("connections"), list)' 2>/dev/null; then
echo "Mihomo 返回了无效的 connections JSON" >&2
exit 1
fi
Parse and display
Pass the filter value via an environment variable to avoid inserting user input into the Python source code:
printf '%s' "$DATA" | FILTER="$ARGUMENTS" python3 -c '
import json
import os
import sys
from collections import defaultdict
data = json.load(sys.stdin)
process_filter = os.environ.get("FILTER", "").strip().lower()
results = []
for connection in data.get("connections", []):
metadata = connection.get("metadata", {})
process = metadata.get("process", "unknown")
if process_filter and process_filter not in process.lower():
continue
host = metadata.get("host", "") or metadata.get("destinationIP", "")
port = metadata.get("destinationPort", "")
rule = connection.get("rule", "")
payload = connection.get("rulePayload", "")
if payload:
rule += "/" + payload
chains = connection.get("chains", [])
chain_text = " → ".join(reversed(chains)) if chains else "DIRECT"
results.append({
"process": process,
"host": f"{host}:{port}" if port else host,
"rule": rule,
"chain": chain_text,
})
grouped = defaultdict(list)
for result in results:
grouped[result["process"]].append(result)
if not grouped:
target = process_filter or "任何进程"
print(f"未找到 {target} 的活跃连接")
raise SystemExit(0)
for process, connections in sorted(grouped.items()):
print(f"\n进程: {process} ({len(connections)} 个连接)")
route_stats = defaultdict(lambda: {"count": 0, "hosts": set()})
for connection in connections:
key = "{} → {}".format(connection["rule"], connection["chain"])
route_stats[key]["count"] += 1
route_stats[key]["hosts"].add(connection["host"])
for route, info in sorted(route_stats.items(), key=lambda item: -item[1]["count"]):
hosts = sorted(info["hosts"])
shown = ", ".join(hosts[:5])
suffix = f" ... (+{len(hosts) - 5})" if len(hosts) > 5 else ""
print(f" 线路: {route}")
print(" 连接数: {}".format(info["count"]))
print(f" 目标: {shown}{suffix}")
'
Explain the results
chains[-1]is the policy group hit, andchains[0]is the real egress; when displaying, reverse it to "policy group → egress".- Common Claude processes are
claudeorClaude Helper; common Codex CLI processes arecodex。 - For Gemini CLI, the ChatGPT desktop app, and browsers, the process names are based on the unfiltered connection table.
- When the Host is only a bare IP, check whether
sniffer.parse-pure-ipand system DNS bypass Clash; do not directly change the AI policy group. - When the current profile is not the expected Hub, or the egress is still the original subscription node, report drift; this skill does not modify YAML.
Completion conditions
- At least one Mihomo endpoint returns valid connections JSON.
- The output clearly shows the process, rule, policy group, and egress, or clearly states that the target process currently has no active connections.
- The entire process is read-only and no API secret is printed.