Releases: LEADBERG-studio/tunnellio-api-client
Release list
v0.6.6
tunnellio 0.6.6
Release summary
One line of behaviour, and it is diagnostic only: the client now tells the bridge
which client it is. Nothing else changed, and nothing on the server side is
required - the field was already read, recorded and displayed. It was simply
always empty.
Small as it is, this closes a hole that cost real time. During a two-session hunt
for a bridge fault, every server log line about an opened session ended with
version: not given. "Who is connected" was answerable; "with what version are
we talking" was not.
Highlights
- The
helloframe carriesclientVersionastunnellio-client/<version>. - It appears in the connection journal, in the live-channels view of the console,
and indocker logs tcp-bridgeon the server. - It also answers the operational question that comes before any strict rule:
who is left to update.
Installation
Windows: download tunnellio.exe and run it. No install step, no dependencies.
Any platform: take tunnellio-source-v0.6.6.zip, then
pip install .
Requires Python 3.11 or newer.
Upgrading
Drop-in. Command line, config files and the bridge protocol are unchanged, so
0.6.5 and 0.6.6 can run side by side against the same server.
Changelog
The client says which client it is
The server has recorded the client version since the bridge existed, in the
journal and in its own log. Nobody ever filled the field, so every session read
clientVersion: not given - including in the log lines that were supposed to
settle arguments about which side was at fault.
- The
helloframe now carriesclientVersion(tunnellio-client/<version>).
It shows up in the connection journal, in the live-channels view, and in
docker logson the server.
That is the whole release. It changes no behaviour and requires nothing on the
server side: the field was already read and stored, it was simply always empty.
It matters because it answers the second question of every incident, right after
"who is connected": with what version are we talking, and who is left to update.
Artifacts
tunnellio.exe- standalone Windows x64 binarytunnellio-windows-x64-v0.6.6.zip- the same binary, zippedtunnellio-source-v0.6.6.zip- platform-neutral source archive, built from the
release commitSHA256SUMS.txt- checksums for everything above
v0.6.5 - stable tcp-bridge
Tunnellio v0.6.5
Коротко
- Мост снова поднимается сам после обрыва связи. До этой версии не поднимался - ни на платном тарифе, ни на бесплатном, и это та самая причина, по которой на него жаловались. Переподключение в клиенте есть с 0.6.3 и работает, когда обрыв видно. Обрыв в дороге не видно: ни FIN, ни RST ни до кого не доходят, сокет остаётся открытым, - и обе половины были устроены так, что верили молчанию. Клиент стучал, сервер не отвечал, поэтому канал был молчащим по построению, а мёртвый туннель ничем не отличался от простаивающего.
- Ровно поэтому SSH переподключался всегда, а мост никогда. У SSH есть
ServerAliveInterval. У моста был стук, на который никто не отвечал.
Что изменилось
- Молчание стало приговором, а не состоянием. Сервер отвечает на каждый стук, и у клиента появился признак жизни, который не стоит ни одного байта данных. Молчание дольше 45 секунд (три пропущенных ответа) закрывает сеанс и передаёт дело уже существующему переподключению. Адрес при этом не меняется.
- Сервер больше не блокирует сам себя при переподключении. Прощание с мёртвым сокетом держало единственный общий замок, а прощание с мёртвым сокетом длится столько, сколько ядро повторяет попытки, то есть минуты. Всё это время мост стоял целиком: посетитель не мог найти сеанс, соединение не принималось, новое рукопожатие не регистрировалось. Туннель не возвращался вообще. Теперь под замком только обмен записями в таблицах.
- Живость проверяет и ядро. Keepalive на управляющем соединении со сроками 30/10/3 вместо двух часов по умолчанию, с обеих сторон: мёртвый канал замечается даже когда через туннель ничего не идёт.
- Санация того, что остаётся после обрыва. Соединение, у которого своя программа не приняла сокет, закрывается сразу, а не висит у сервера в очереди, пока посетитель ждёт свой таймаут. Список обслуженных соединений в клиенте чистится - он не чистился никогда, и у долгоживущего туннеля рос с каждым запросом. Посетитель, которого не удалось передать клиенту, снимает сеанс, а не оставляет его глотать всех следующих.
Как пользоваться
- В настройках, командах и флагах не изменилось ничего. Существующий профиль продолжает работать.
- Два срока при необходимости правятся в
src/tunnellio/bridge.py:HEARTBEAT_INTERVAL(15 с) иSILENCE_LIMIT(45 с). Предел должен оставаться не меньше трёх интервалов, иначе один потерянный пакет будет прочитан как обрыв. - Честное ограничение: между обрывом и тем, как клиент его заметит, проходит до 45 секунд. Посетители, пришедшие в это окно, получают
504 Gateway Timeoutпримерно через 15 секунд - это срок недоставленного соединения на сервере. Внятный отказ лучше страницы, которая грузится минуту, но это не нулевой простой, а сократить его можно только более частым стуком.
Changelog
- Сервер моста отвечает на heartbeat. Раньше не мог: клиент стучит раз в 15 секунд, а ожидание чтения у сервера истекает через 30, поэтому свой heartbeat у сервера не наступал никогда и канал со стороны клиента был вечно молчащим.
- Молчание дольше
SILENCE_LIMIT(45 с) считается оборванным каналом: сеанс закрывается, дальше работает уже существующее переподключение с нарастающей паузой, под тем же именем. - На управляющем соединении включён TCP keepalive со сроками 30/10/3 - и в клиенте, и на сервере - вместо двух часов по умолчанию.
- Сервер больше не прощается с прежним сеансом под общим замком. Прощание и ответы застрявшим посетителям уходят в фон со сроком, а сокет, не ответивший за этот срок, обрывается силой.
- Посетитель, которого не удалось передать клиенту моста, снимает сеанс, а не оставляет его глотать каждого следующего.
- Соединение, у которого своя программа не приняла сокет, закрывает свою сторону сразу, а не остаётся в очереди сервера.
- Клиент чистит список потоков обслуженных соединений. Он не чистился никогда, и у долгоживущего туннеля рос с каждым запросом.
- В ответе
503застрявшему посетителю правильно указана длина тела: она была на байт меньше, и получатель дочитывал ответ до обрыва связи вместо аккуратного конца. - Снимок машины в мониторе моста снимается до замка, а не под ним.
Артефакты
tunnellio.exe- Windows x64, самостоятельный, Python не нуженtunnellio-windows-x64-v0.6.5.zip- бинарник вместе с README, CHANGELOG,config.example.jsonиdocs/tunnellio-source-v0.6.5.zip- исходники из тегаv0.6.5, без привязки к системеSHA256SUMS.txt
0.6.1 - fix tcp-bridge
Tunnellio v0.6.1
Коротко
TCP-мост наконец поднимает туннель. Настроенный домен у зарегистрированного
пользователя не публиковался вовсе, а случайный только выглядел работающим,
потому что его никто ни о чём не спрашивал. Мешало шесть разных поломок
одновременно, и каждой по отдельности хватало, чтобы туннеля не было.
Главное
- Настроенный домен работает — и по токену, и просто с регистрацией.
POST /v1/sessions/openбольше не требуетkeyId. В мосту нет SSH, значит
нет и ключа, который можно назвать. Шесть источников уже отвечали
requiresSshKey: false; седьмая, проверка запроса, говорила обратное и
побеждала. - Ключ адреса. Выдаётся вместе с поддоменом, приезжает владельцу в
connectionProfile.tcpBridge.tokenи идёт в кадре приветствия. До этого любой,
кто знал чужой поддомен, забирал живой туннель себе: прежний сеанс закрывался
какreplaced, и никто ничего не спрашивал. - Рукопожатие завершается. Сервер отвечает публичным портом, и клиент
перестал считать успешный вход провалом. - Туннель стоит. Тишина в управляющем канале больше не читается как разрыв,
а клиент сам подаёт голос раз в пятнадцать секунд, чтобы домашний роутер не
забыл сопоставление за ночь. - OAuth через мост отвергается честно. Мост передаёт байты насквозь, токен
проверить негде. Раньше такой домен выглядел защищённым в консоли и был
открыт всему интернету.
Что работает и при каких условиях
| Ситуация | Адрес | Мост | Цена |
|---|---|---|---|
| Без учётной записи, без ключей и токена | случайный tmp-xxxxxxxx, 24 часа |
работает | бесплатно |
| Регистрация, домен настроен на мост | свой постоянный поддомен | работает | бесплатно |
| На домене задан пароль моста | свой поддомен, нужен пароль | работает | Pro |
| Работа через API по токену, адрес любой | настроенный или случайный | работает | Pro |
- Постоянный поддомен по-прежнему только для зарегистрированного: это имя в
нашем домене на годы, и за ним обязан стоять человек. Случайный адрес живёт
сутки и умирает сам. - На тарифе Free до 10 доменов; лишние помечаются
plan_limited. authMode: oauthиdualнесовместимы сconnectionMode: tcp_bridgeи
auto. Для OAuth — Cloud proxy, для моста — Legacy.
# Без всего, случайный адрес
.\tunnellio.exe connect --transport tcp-bridge --domain random --local-port 8787 --run
# Регистрация и настроенный домен, SSH-ключа нет нигде
.\tunnellio.exe --token <ТОКЕН> connect --domain existing:tuntun ^
--local-host 127.0.0.1 --local-port 8787 --transport tcp-bridge --run --watch --name myvpn-files
# Домен с паролем моста (Pro)
.\tunnellio.exe --token <ТОКЕН> connect --domain existing:demo ^
--tcp-bridge-password <ПАРОЛЬ> --local-port 8787 --transport tcp-bridge --runЧто изменилось
- Ключ адреса уходит в кадре приветствия и подтверждает право публиковать под
этим именем. - Ключ отдаётся всегда, когда сервер его прислал, а не только когда потребовал:
мягкий сервер тоже должен знать, с кем говорит. - Пароль моста больше не берётся из служебного токена. Это разные вещи, и
подстановка давала гарантированныйinvalid_bridge_passwordна каждом домене
с паролем. - Рукопожатие с загадкой пропускается для родного протокола. Наш мост не
присылаетChallenge, поэтому каждое соединение стояло до таймаута, а
выглядело это как «туннель поднялся и не отвечает». - Тишина в управляющем канале не принимается за разрыв. Ждали полсекунды,
сервер подаёт голос раз в тридцать: мост умирал через полсекунды после
успешного рукопожатия. - Клиент сам подаёт голос раз в пятнадцать секунд. Никто этого не требует, но
домашний роутер забывает простаивающее сопоставление молча, и туннель,
простоявший ночь, наутро уже никуда не ведёт. - Отсутствие публичного порта больше не считается провалом рукопожатия:
HTTP-мост публикует по имени, и номера порта в ответе может не быть. - Идентификатор соединения посылается под двумя именами,
idиconnectionId.
Сервер читает первое, мы посылали второе, и каждое соединение заканчивалось
connection_not_found. - Предел кадра поднят до 8192 байт, как на сервере. 256 хватало ровно до того
дня, когда в приветствии появились адрес и порт. .well-known/oauth-protected-resourceбольше не запрашивается у публичного
адреса на legacy- и бридж-доменах: там стоит чужая программа, она отвечает
404, и этот 404 попадал в лог как ошибка.Runtime name:печатается один раз; второй, пустой, убран.docs/TCP_BRIDGE.mdиdocs/ru/TCP_BRIDGE.mdприведены к тому, как протокол
работает на самом деле: ключ адреса, коды отказов, ограничение по OAuth.
Со стороны сервера, для справки (репозиторий tunnellio)
POST /v1/sessions/openпринимает запрос безkeyId, если режим домена
tcp_bridgeилиauto. Явно неверныйkeyIdвида-5по-прежнему
validation_error, а Direct SSH без ключа так и остаётся невозможным.- Сессия без ключа больше не стирает ключ, уже настроенный на домене.
heartbeat,resumeиcloseпроверены на сессии без ключа; ключа они не
требовали и не требуют.- Мост отвергает незнакомое имя (
unknown_hostname), неверный ключ владельца
(invalid_bridge_key, всегда) и недоступную базу (bridge_unavailable)
вместо того, чтобы пускать всех. Клиент вовсе без ключа — по
TCP_BRIDGE_OWNER_KEY_REQUIRED; пока0, такой клиент проходит и
записывается какbridge.legacy. - Приветствие сервера несёт публичный порт, публичный адрес и период голоса.
oauth_requires_proxyотвергает сочетание моста и OAuth и при создании
домена, и при открытии сессии.- Ответ о сессии называет свой
keyId, включая его отсутствие.
Файлы
tunnellio.exetunnellio-windows-x64-v0.6.1.ziptunnellio-source-v0.6.1.zip
Как проверялось
- Тесты: 91 в клиенте, 154 на сервере.
- Живая проверка насквозь, на настоящих сокетах, а не на заглушках: настоящий
управляющий порт моста, настоящий публичный порт, настоящий локальный сервис и
запрос снаружи. Настроенный домен по токену открыл сессию без ключа, запрос
дошёл до локального сервиса и вернулся, удар сердца сессию не убил, а чужой
ключ адреса отвергнут сinvalid_bridge_key. Проверка лежит в репозитории
сервера какtests/live_bridge_check.py— именно потому, что модульные тесты
обеих половин были зелёными, пока половины не работали вместе.
Порядок обновления
- Сначала деплой сервера: клиент присылает ключ адреса, и что с ним делать,
знает только исправленный сервер. - Когда новый клиент разойдётся, поставить на сервере
TCP_BRIDGE_OWNER_KEY_REQUIRED=1. До этого старые клиенты продолжают
подключаться и видны в журнале какbridge.legacy— по этой строке и понятно,
кого осталось обновить. - Конфигурации, команды и флаги не менялись.
v0.6.0 stable
Tunnellio v0.6.0
Summary
- The API token is now the most capable credential, not a hard prerequisite. Three connection modes work entirely without it, and a plan limit is no longer reported as an authentication failure.
Highlights
- Credential-aware modes. With an API token everything works. With only an SSH key you still get
ssh_stable,tcp_stableandtcp_random. With no credentials at all the two keyless bridge modes still work. In those three modes the API token is never read and never validated. - Plan limits are not auth failures.
403 plan_requiredon the advisoryPOST /v1/metaused to become anAuthErrorand abort the entireconnect, even for transports that never needed that metadata. It is now a distinctPlanRequiredErrorand the launch continues. - Offline SSH plan. A reserved domain plus its bound key already contains everything needed to connect, so the client builds the SSH command locally with no API call.
Availability matrix
| Credentials | Available modes |
|---|---|
| API token | everything: full API flow, ssh_stable, tcp_stable, tcp_random |
| SSH key only | ssh_stable, tcp_stable, tcp_random |
| nothing | tcp_stable, tcp_random |
# No credentials at all
.\tunnellio.exe connect --transport tcp-bridge --domain random --local-port 3000 --run
# Reserved domain, still no API token
.\tunnellio.exe connect --transport tcp-bridge --domain existing:mcp --local-port 3000 --run
# Reserved domain plus your own SSH key, still no API token
.\tunnellio.exe connect --transport ssh --domain existing:mcp --local-port 3000 --runArtifacts
tunnellio.exetunnellio-windows-x64-v0.6.0.ziptunnellio-source-v0.6.0.zip
Usage notes
- Operations that genuinely use the Integration API still require a token: creating a domain or key, cloud proxy, OAuth flows and
--transport auto. Nothing there changed. POST /v1/metaandPOST /v1/capabilitiesare advisory. When a plan refuses them, whatever was unavailable is listed in thedegradedarray of the JSON result.- When capabilities are unknown, the client no longer enforces local guesses about what the account may do. The server stays the source of truth and rejects anything it does not allow. When capabilities are known, every gate behaves exactly as before.
- Backward compatible: existing configs, commands and flags are unchanged.
Changelog
- Added
tunnellio/modes.pywith the credential matrix:resolve_mode(),available_modes(),requires_api_token(),requires_ssh_key(). connectderives the token requirement from the mode the launch resolves to, instead of demanding one up front.- Added
Planner.build_offline_ssh_plan()for direct SSH to a reserved domain with no API call. connectroutes to the keyless bridge endpoint automatically fortcp_stableandtcp_randomwhen no token is configured.- Added
PlanRequiredError,PLAN_REQUIRED_CODESandis_plan_limit(); a plainforbiddenwith no plan wording is still anAuthError. - Added
Capabilities.known/Capabilities.unknown()andPlanResult.degraded. - Fixed
bridge --save-profile, which always crashed because_save_profile()calledmeta.to_dict()while the keyless path passesmeta=Noneby design. - Added
tests/test_errors.pyandtests/test_modes.py; 91 tests pass across errors, modes, planner, cli, client, bridge, config and oauth.
Verification
- Live-checked with the shipped binary and no API token:
ssh_stablebuilds the correct reverse-forward command locally,tcp_stableandtcp_randomreach the public keyless endpoint and receive a real server-issued domain. - Confirmed that an API-only flow (
--domain new:...) still refuses to run without a token.
Notes for GitHub publication
- Upload the binary and zip from this release subfolder.
- Recheck that the changelog matches
CHANGELOG.mdfor the target version. - Recheck that the version number in the title matches the shipped artifacts.
0.5.0 - TCP tunnel adding (+ private mode)
Tunnellio v0.5.0
Summary
- This release adds native TCP bridge password support, a fully self-sufficient pure-Python TCP bridge client, and a keyless public launch endpoint.
- The client now works on Windows, Linux, and macOS without any external binaries — the bridge is implemented natively inside the package.
Highlights
- Native TCP bridge client (
src/tunnellio/bridge.py) — pure Python, no external binaries, bore-compatible wire protocol with HMAC-SHA256 auth - Keyless
bridgecommand —tunnellio bridge --local-port 3000 --runworks without API token, without SSH key, without any external binary - TCP bridge password —
--tcp-bridge-passwordflag,passwordRequiredandclientProtocolin connection profile, automatic password in hello/accept frames - Native Tunnellio wire format —
{"type":"hello","hostname":"...","password":"..."}whenclientProtocol.hellois present, bore-format fallback otherwise - Public keyless endpoint —
POST /v1/tcp-bridge/launchwithout Bearer auth - Auto-fallback —
--transport autotries SSH first, falls back to TCP bridge on quick failure - Cleaned repository — removed stray nested clone and obsolete bootstrap script
Artifacts
tunnellio.exe— standalone Windows binary (Python not required on target machine)tunnellio-source-v0.5.0.zip— universal source archive (Python 3.11+ required)
Usage notes
Simplest tunnel (no token, no key, no external binary)
.\tunnellio.exe bridge --local-port 3000 --run --name my-bridgeWith a chosen subdomain
.\tunnellio.exe bridge --domain new:my-app --local-port 3000 --run --name my-bridgeWith TCP bridge password
.\tunnellio.exe --token YOUR_TOKEN connect --domain new:my-app --local-port 3000 --transport tcp-bridge --tcp-bridge-password demo-secret --run --name my-bridgeIn a Unix sandbox
unzip tunnellio-source-v0.5.0.zip -d tunnellio && cd tunnellio
pip install .
tunnellio bridge --local-port 3000 --run --name my-bridgeTransport comparison
| Command | Token | SSH key | External binary | Platform |
|---|---|---|---|---|
bridge |
no | no | no | Windows, Linux, macOS |
connect --transport tcp-bridge |
yes | no | no | Windows, Linux, macOS |
connect --transport ssh |
yes | yes | OpenSSH | any |
connect --transport auto |
yes | yes | OpenSSH | any |
Changelog
- Added TCP bridge password support (
tcpBridgePassword,passwordRequired,clientProtocol) - Added
--tcp-bridge-passwordCLI flag forplan,connect, andbridgecommands - Added
TcpBridgeClientProtocolmodel withhellotemplate from server - Added
passwordRequiredandclientProtocolfields toTcpBridgeProfile - Added
tcpBridgePasswordfield toDomainSummary - Updated
bridge.pyto send native Tunnellio wire format whenclientProtocol.hellois provided - Updated
bridge.pyto send{"type":"accept","connectionId":"...","password":"..."}for connection accept - Updated
cli.pyto passhello_template,password,password_requiredtolaunch_bridge() - Updated
planner.pyto passtcpBridgePasswordin launch-spec domain block and keyless bridge payload - Updated config template and config example with
tcpBridgePasswordfield - Added tests for password flow, hello template, and CLI flag parsing
- Backward compatible: bore-protocol mode still works when
clientProtocolis absent - Implemented native TCP bridge client in pure Python (
tunnellio/bridge.py) — no external binaries needed - Wire protocol: null-delimited JSON frames, bore-compatible control handshake, HMAC-SHA256 auth
cli.pyruns the bridge natively vialaunch_bridge()instead ofsubprocess.Popenfor tcp_bridge transportTcpBridgeProcessprovides the samepid/poll/terminate/kill/waitinterface assubprocess.Popen- Bidirectional TCP copy via
selectfor connection forwarding - Client is now fully self-sufficient on Windows, Linux, and macOS — only Python 3.11+ required
- Added
bridgeCLI command for one-shot keyless TCP bridge tunnels — no API token, no SSH key required - Added public keyless endpoint
POST /v1/tcp-bridge/launch(no Bearer auth) in ApiClient - Added
Planner.build_keyless_bridge_plan()that skips meta/capabilities/launch-spec and calls the public endpoint directly - Added
requiresApiTokenfield toConnectionProfileandTcpBridgeProfile - Added
is_tokenlessproperty toConnectionProfile - Made
PlanResult.metaandPlanResult.domainoptional (None for keyless bridge flow) - Made
bridgecommand exempt from the API token requirement - Added TCP bridge transport (
connectionMode = tcp_bridge) as a keyless alternative to reverse SSH - Added
--transportCLI flag (ssh,tcp-bridge,auto) for explicit transport selection - Added automatic fallback from SSH to TCP bridge when
--transport autois used and SSH fails quickly - Removed stray nested clone and obsolete bootstrap script from repository
Notes for GitHub publication
- Upload
tunnellio.exeandtunnellio-source-v0.5.0.zipfrom this release subfolder. - Version:
0.5.0(verified inpyproject.tomlandsrc/tunnellio/__init__.py). - Commit:
813d289 - Changelog matches
CHANGELOG.md.
v0.1.7 OAuth clients contract
Tunnellio v0.1.7
Summary
- This release aligns the shipped version number, documentation, and release artifacts to 0.1.7.
- It includes the live OAuth Authorization Code + PKCE client flow and validated release packaging.
- The client works with the real server-side OAuth contract and includes corrected token refresh persistence behavior.
Highlights
- Live OAuth Authorization Code + PKCE flow through
oauth-login,oauth-refresh, andoauth-introspect. - Discovery and protected-resource metadata support aligned with the real server contract.
- Refresh flow preserves the previous
refreshTokenwhen the server omits a new one.
Artifacts
tunnellio.exetunnellio-source-v0.1.7.ziptunnellio-windows-x64-v0.1.7.zip
Usage notes
- The server remains the source of truth for final
authModeandconnectionMode. - For saved OAuth sessions, use
--token-nameor--token-filewith the OAuth commands.
Changelog
- Aligned the client with the new discovery, auth, and session contract.
- Added OAuth discovery helpers, protected-resource metadata loading, PKCE utilities, and extended session-aware planning/runtime metadata.
- Added live-validated session lifecycle handling with resume-token aware heartbeat, resume, and close calls.
- Expanded config, runtime snapshots, tests, and docs for the new server contract.
Notes for GitHub publication
- Upload the binary and zip from this release subfolder.
- Version corrected to
0.1.7. - Commit:
eb0612eebc23d1f1088cc8e2ccd603691211e6b5
v0.1.6 new clients contract
Tunnellio v0.1.6
Summary
- This release aligns the client with the new Tunnellio discovery, auth, and session contract.
- The client now reflects the server's final auth/connection contract, supports OAuth discovery helpers, and keeps richer runtime/session metadata.
- Release validation included local source checks, the expanded manual suite, and live plan/session verification against
test.tunnellio.site.
Highlights
- Added OAuth discovery helpers and PKCE utilities.
- Added session-aware runtime handling for
open,heartbeat,resume,close, andcompleteflows. - Added resume-token aware heartbeat/close handling and richer runtime snapshot metadata.
- Expanded config, planner, tests, and documentation for the new server contract.
Artifacts
tunnellio.exe— standalone Windows binarytunnellio-source-v0.1.6.zip— universal Python/source archive
Usage notes
- The server remains the source of truth for the final
authModeandconnectionMode; callers should read the returned runtime snapshot instead of assuming requested modes were granted unchanged. - For random or ephemeral domains, launch with a runtime name and then call
show-config --name <runtime>to read back the final server-side hostname and public URL. - The ready binary is Windows-specific; the source archive is the portable project package for Python/OpenSSH environments.
Changelog
- Aligned the client with the new discovery, auth, and session contract.
- Added OAuth discovery helpers, PKCE utilities, and extended session-aware planning/runtime metadata.
- Added live-validated session lifecycle handling with resume-token aware heartbeat, resume, and close calls.
- Expanded config, runtime snapshots, tests, and docs for the new server contract.
Notes for GitHub publication
- Upload both
tunnellio.exeandtunnellio-source-v0.1.6.zipfrom this folder. - Reuse this Markdown as the GitHub release body.
- Publish the release from the
maincommit for v0.1.6 after creating the GitHub release.