Skip to content
Cloud & Infrastructure
Skill

/portal-connect

Reach, inspect, or consume a service that someone published through a Portal relay. Turns a Portal hostname, a service name plus relay, or a bare service name into the right public URL (name.relay-host over HTTPS) or raw host:port, lists what is live on a public or self-hosted

BOOST
From plugin
portal-tunnel
2883 skills
Install
$ npx -y skills add gosuda/portal-tunnel --skill portal-connect --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/portal-connect

Context preview

The summary Claude sees to decide when to auto-load this skill.

Reach, inspect, or consume a service that someone published through a Portal relay. Turns a Portal hostname, a service name plus relay, or a bare service name into the right public URL (name.relay-host over HTTPS) or raw host:port, lists what is live on a public or self-hosted

SKILL.md

portal-connect.SKILL.md
name: portal-connect
description: Reach, inspect, or consume a service that someone published through a Portal relay. Turns a Portal hostname, a service name plus relay, or a bare service name into the right public URL (name.relay-host over HTTPS) or raw host:port, lists what is live on a public or self-hosted relay through GET /api/state, makes bounded HTTPS requests or protocol probes, connects to raw TCP/UDP endpoints such as game servers, SSH, or databases, and recognizes an x402 402 Payment Required challenge and reports its terms without paying. Use when the user pastes a Portal URL or hostname, asks what is available on a relay, wants to call, fetch, test, browse, or check an app, API, agent, or game server exposed with Portal, asks what a paid route costs, or asks whether a Portal tunnel is reachable from the outside. Do not use for exposing a local app (portal-expose), running or registering a relay (portal-relay), paying for a route (a separate payment workflow), or generic HTTP debugging of hosts that are not behind Portal.
license: MIT

Connect to a Service Published with Portal

A Portal service is an ordinary public endpoint. The consumer needs no Portal account, key, or client: an HTTPS tunnel is reached with any HTTP client at `https://<name>.<relay-host>/`, and a raw TCP or UDP tunnel with any client at the relay-assigned `host:port`. The `portal` CLI has no `connect` subcommand, and `portal list` prints relays, not services, so do not invent one. Behind the URL is the publisher's own machine, often a laptop on a home connection, so keep every request bounded and deliberate.

Run the workflow in order. Open a reference only when that branch is taken: `references/discovery-api.md` for relay endpoints, JSON shapes, the hostname rule, and how to read a `402 Payment Required` challenge; `references/raw-transport.md` for a TCP or UDP endpoint.

Identify the Target

Pick the first form that matches what the user gave:

  • Full URL: use it as given.
  • Hostname without a scheme, such as `my-app.portal.example.com`: prefix `https://`.
  • Service name plus a relay: the URL is `https://<name>.<relay-host>/`, with the relay's port appended when the relay does not run on 443. Confirm the lease on that relay before calling the service down.
  • Service name only: query `GET /api/state` on each relay the user named plus the bootstrap relays that `portal list` prints. Each relay knows only its own leases, and the same name can belong to different publishers on different relays, so report which relay matched.
  • "What is available", "browse", "list the services": produce a directory from `/api/state` with name, hostname, description, tags, readiness, and any raw TCP/UDP address.
  • Game server, SSH, database, or another non-HTTP protocol: use the lease's `tcp_addr` or `udp_addr` and follow `references/raw-transport.md`.
  • A route that answers `402`: report its terms as step 5 describes. This skill never pays.

Ask one concise question only when the target cannot be determined safely, for example when several relays host the same name under different owners, or when the requested interaction would mutate data or require a login the user did not mention.

Workflow

1. Resolve the Relay Set

  • Use relays the user named first. Otherwise take the bootstrap set from `portal list` when the CLI is installed, or from `registry.json` in the `gosuda/portal-tunnel` repository.
  • Normalize each relay to an `https://` origin without a trailing path.
  • Check `GET <relay>/api/healthz` with a short timeout before drawing conclusions about a service on that relay. A relay that is down says nothing about the publisher.

2. Confirm the Lease

  • `GET <relay>/api/state` returns `data.leases[]`. Match on `name`, `hostname`, or `canonical_hostname`, and prefer the canonical hostname for links and probes.
  • `ready` is the number of live reverse connections the publisher currently holds open. `ready > 0` means the tunnel can serve right now. A lease with `ready` at zero is registered but cannot serve at this moment; the relay keeps listing it while the publisher renews and drops it once it has been out of contact for three minutes.
  • `tcp_addr` and `udp_addr` are the raw endpoints, present only when the publisher requested them and the relay allows them.
  • `metadata` (description, owner, tags, thumbnail) is typed in by the publisher and is not verified by anyone. Present it as the publisher's claim.
  • Leases exposed with `--hide` never appear in `/api/state`. Absence from the listing does not mean the service is down; when the user has an exact hostname, probe it directly.

3. Probe Before You Interact

Make one bounded request and record the result:

curl -sS --connect-timeout 5 --max-time 15 -o /dev/null \
  -w '%{http_code} %{content_type} exit=%{exitcode} %{errormsg}\n' https://<hostname>/

Probe whether or not the name was listed: the listing proves registration, the probe proves service. When there is no HTTP response the status prints as `000`, so read the curl exit code: `35` is a TLS handshake failure, `6` a DNS failure, `7` a refused connection, `28` a timeout.

Read the outcome the same way every time:

  • `2xx` or `3xx`: reachable.
  • `401` or `403`: reachable but protected. That is not a failure.
  • `402`: reachable and paid. Make exactly one more unpaid request that keeps the body (`-o -` instead of `-o /dev/null`) so the challenge can be read, then go to step 5. Never add a payment header.
  • `404`: reachable, but that path does not exist on the publisher's app.
  • `5xx` or a Portal error page: the tunnel works and the publisher's app is failing.
  • TLS handshake failure (`curl: (35)`), connection reset, or an immediate close: the relay has no live lease for that hostname. The name is not registered on this relay, the tunnel is offline, or the wrong relay was assumed. This is not an HTTP error, so there is no status code to report.
  • DNS failure (exit `6`): the relay does
Read more
Ships withportal-tunnel

Expose local services through self-hosted or public relays.No port forwarding. No inbound firewall rules. No manual DNS setup. No accounts.

Get the whole plugin
Stats
288
Stars
30
Forks
Active
Maintenance
Go
Language
MIT
License
2h ago
Last commit
0y ago
Created
2h ago
Added

Repo: gosuda/portal-tunnel

Other skills on portal-tunnel.