Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .github/scripts/dns-aid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
"""Upsert DNS-AID records on the tinyclient.io zone and turn DNSSEC on.

The site itself has no A2A or MCP listener, so the only record is the
organization index at _index._agents, aimed at the website over HTTPS.
A TXT record at _catalog._agents points at the ARD manifest.

DNSSEC signing starts in Cloudflare immediately. Validating resolvers
return authenticated data only after the registrar publishes the DS
record this script prints.
"""

import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request

TOKEN = os.environ.get("CLOUDFLARE_API_TOKEN", "")
ZONE_NAME = "tinyclient.io"
API = "https://api.cloudflare.com/client/v4"


def request(method, path, body=None):
data = None if body is None else json.dumps(body).encode()
req = urllib.request.Request(
API + path,
data=data,
method=method,
headers={
"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json",
},
)
try:
with urllib.request.urlopen(req) as resp:
payload = json.load(resp)
except urllib.error.HTTPError as err:
detail = err.read().decode("utf-8", "replace")
raise SystemExit(f"{method} {path} failed: {err.code} {detail}")
if not payload.get("success", False):
raise SystemExit(f"{method} {path} failed: {json.dumps(payload)}")
return payload


def zone_id():
query = urllib.parse.urlencode({"name": ZONE_NAME})
payload = request("GET", "/zones?" + query)
zones = payload.get("result") or []
if not zones:
raise SystemExit(f"no Cloudflare zone named {ZONE_NAME}")
return zones[0]["id"]


def upsert(zid, record):
name = record["name"]
if not name.endswith("." + ZONE_NAME):
fqdn = name + "." + ZONE_NAME
else:
fqdn = name
query = urllib.parse.urlencode({"type": record["type"], "name": fqdn})
found = request("GET", f"/zones/{zid}/dns_records?{query}").get("result") or []
if found:
existing = found[0]
request("PUT", f"/zones/{zid}/dns_records/{existing['id']}", record)
print(f"updated {record['type']} {fqdn}")
return
request("POST", f"/zones/{zid}/dns_records", record)
print(f"created {record['type']} {fqdn}")


def dnssec(zid):
current = request("GET", f"/zones/{zid}/dnssec")["result"]
status = current.get("status")
if status != "active":
current = request("PATCH", f"/zones/{zid}/dnssec", {"status": "active"})["result"]
print("DNSSEC status set to", current.get("status"))
else:
print("DNSSEC already active")
print("Add this DS at the registrar if it is not there yet:")
print(
" ".join(
str(current.get(key, ""))
for key in ("key_tag", "algorithm", "digest_type", "digest")
)
)


def main():
if not TOKEN:
raise SystemExit("CLOUDFLARE_API_TOKEN is not set")
zid = zone_id()
upsert(
zid,
{
"type": "HTTPS",
"name": "_index._agents",
"ttl": 3600,
"proxied": False,
"comment": "DNS-AID organization index. Target is the public website.",
"data": {
"priority": 1,
"target": "tinyclient.io",
"value": 'alpn="h2" port=443',
},
},
)
upsert(
zid,
{
"type": "TXT",
"name": "_catalog._agents",
"ttl": 3600,
"proxied": False,
"content": "url=https://tinyclient.io/.well-known/ai-catalog.json",
"comment": "ARD manifest location.",
},
)
dnssec(zid)


if __name__ == "__main__":
try:
main()
except SystemExit:
raise
except Exception as err:
print(err, file=sys.stderr)
raise SystemExit(1)
9 changes: 8 additions & 1 deletion .github/workflows/site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: Site
on:
push:
branches: [main]
paths: ["site/**", ".github/workflows/site.yml"]
paths: ["site/**", ".github/workflows/site.yml", ".github/scripts/dns-aid.py"]
workflow_dispatch:

concurrency:
Expand All @@ -32,3 +32,10 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy site --project-name tinyclient --branch main --commit-hash ${{ github.sha }}
# DNS-AID index for the public site, plus DNSSEC. The token can edit the zone.
# Validating resolvers authenticate the records only after the registrar has the DS
# record this step prints. Failure here leaves the Pages deploy in place.
- name: Publish DNS-AID records
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: python3 .github/scripts/dns-aid.py
12 changes: 12 additions & 0 deletions site/.well-known/agent-skills/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [
{
"name": "tinyclient",
"type": "skill-md",
"description": "Answer questions about tinyclient, a local open-source chat client for Slack, Discord, Telegram, Teams, and Signal. Use when someone asks how to install it, where data lives, or whether tinyclient has an account or API.",
"url": "https://tinyclient.io/.well-known/agent-skills/tinyclient/SKILL.md",
"digest": "sha256:9ca27e94064863bc4be84581dad430920836604296733398e3eb830a906372ee"
}
]
}
24 changes: 24 additions & 0 deletions site/.well-known/agent-skills/tinyclient/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: tinyclient
description: Answer questions about tinyclient, a local open-source chat client for Slack, Discord, Telegram, Teams, and Signal. Use when someone asks how to install it, where data lives, or whether tinyclient has an account or API.
---

# tinyclient

tinyclient is a native desktop app. It has no tinyclient account, no tinyclient server, and no remote API. Messages stay on the computer that runs it. Sessions stay in the OS keychain.

## Say this first

- Downloads: macOS https://tinyclient.io/download/macos , Linux choices https://tinyclient.io/docs/#linux , Windows https://tinyclient.io/download/windows
- Checksums: https://tinyclient.io/download/checksums
- Docs: https://tinyclient.io/docs/
- Privacy: https://tinyclient.io/docs/privacy.html
- Source: https://github.com/itz4blitz/tinyclient (MIT)

## Do not invent

Do not tell a person to register an agent, mint an OAuth token, or call a tinyclient HTTP API. None of those exist. Sign-in happens inside the app, against Slack, Discord, Telegram, Teams, or Signal.

## What the site is

tinyclient.io publishes the landing page, the docs, and stable download redirects. It sets no cookies and loads nothing from other origins.
64 changes: 64 additions & 0 deletions site/.well-known/ai-catalog.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"specVersion": "1.0",
"host": {
"displayName": "tinyclient",
"identifier": "https://tinyclient.io"
},
"entries": [
{
"identifier": "urn:air:tinyclient.io:docs:getting-started",
"displayName": "Getting started",
"type": "text/html",
"url": "https://tinyclient.io/docs/",
"representativeQueries": [
"how do I install tinyclient",
"download tinyclient for macOS Linux or Windows",
"where does tinyclient keep my messages"
]
},
{
"identifier": "urn:air:tinyclient.io:docs:privacy",
"displayName": "Privacy and security",
"type": "text/html",
"url": "https://tinyclient.io/docs/privacy.html",
"representativeQueries": [
"does tinyclient have telemetry",
"where are tinyclient sessions stored",
"what data does tinyclient send"
]
},
{
"identifier": "urn:air:tinyclient.io:docs:architecture",
"displayName": "Architecture",
"type": "text/html",
"url": "https://tinyclient.io/docs/architecture.html",
"representativeQueries": [
"what is chatd in tinyclient",
"how does tinyclient connect to Slack and Signal",
"is there a tinyclient server"
]
},
{
"identifier": "urn:air:tinyclient.io:skill:tinyclient",
"displayName": "tinyclient agent skill",
"type": "text/markdown",
"url": "https://tinyclient.io/.well-known/agent-skills/tinyclient/SKILL.md",
"representativeQueries": [
"what should an agent know about tinyclient",
"does tinyclient have an API or account",
"tinyclient download and privacy summary"
]
},
{
"identifier": "urn:air:tinyclient.io:api:website",
"displayName": "Website API description",
"type": "application/vnd.oai.openapi+json",
"url": "https://tinyclient.io/.well-known/openapi.json",
"representativeQueries": [
"does tinyclient.io expose a chat API",
"openapi for the tinyclient website",
"how do agents authenticate to tinyclient"
]
}
]
}
28 changes: 28 additions & 0 deletions site/.well-known/api-catalog
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"linkset": [
{
"anchor": "https://tinyclient.io/",
"service-desc": [
{
"href": "https://tinyclient.io/.well-known/openapi.json",
"type": "application/vnd.oai.openapi+json",
"title": "OpenAPI description of tinyclient.io"
}
],
"service-doc": [
{
"href": "https://tinyclient.io/docs/",
"type": "text/html",
"title": "tinyclient documentation"
}
],
"describedby": [
{
"href": "https://tinyclient.io/llms.txt",
"type": "text/plain",
"title": "llms.txt"
}
]
}
]
}
22 changes: 22 additions & 0 deletions site/.well-known/openapi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"openapi": "3.1.0",
"info": {
"title": "tinyclient website",
"version": "0.2.5",
"description": "tinyclient is a local desktop application. This origin publishes documentation and download redirects only. There is no remote chat API, no account service, and no token endpoint. Paths is empty on purpose.",
"license": {
"name": "MIT",
"url": "https://github.com/itz4blitz/tinyclient/blob/main/LICENSE"
}
},
"externalDocs": {
"description": "Getting started",
"url": "https://tinyclient.io/docs/"
},
"servers": [
{
"url": "https://tinyclient.io"
}
],
"paths": {}
}
1 change: 1 addition & 0 deletions site/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self'; style-src 'self'; base-uri 'none'; form-action 'none'">
<meta name="referrer" content="no-referrer">
<title>Not found · tinyclient</title>
<meta name="robots" content="noindex">
<meta name="color-scheme" content="dark light">
<link rel="icon" href="/tinyclient/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/tinyclient/style.css">
Expand Down
37 changes: 37 additions & 0 deletions site/_headers
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,40 @@
Cache-Control: public, max-age=0, must-revalidate
/site.js
Cache-Control: public, max-age=0, must-revalidate

# Discovery documents. Link is repeated by the Pages function so a cached HTML
# response still advertises the catalog.
/
Link: </.well-known/api-catalog>; rel="api-catalog", </docs/>; rel="service-doc", </.well-known/openapi.json>; rel="service-desc", </llms.txt>; rel="describedby", </.well-known/ai-catalog.json>; rel="describedby"
/index.html
Link: </.well-known/api-catalog>; rel="api-catalog", </docs/>; rel="service-doc", </.well-known/openapi.json>; rel="service-desc", </llms.txt>; rel="describedby", </.well-known/ai-catalog.json>; rel="describedby"

/robots.txt
Content-Type: text/plain; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/sitemap.xml
Content-Type: application/xml; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/llms.txt
Content-Type: text/plain; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/auth.md
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/.well-known/api-catalog
Content-Type: application/linkset+json; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/.well-known/openapi.json
Content-Type: application/vnd.oai.openapi+json; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/.well-known/ai-catalog.json
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
Cross-Origin-Resource-Policy: cross-origin
Cache-Control: public, max-age=0, must-revalidate
/.well-known/agent-skills/index.json
Content-Type: application/json; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/.well-known/agent-skills/tinyclient/SKILL.md
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
21 changes: 21 additions & 0 deletions site/auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# auth.md

tinyclient does not run an account service. There is no agent registration, no client id, and no credential to claim or revoke.

The desktop app signs in to Slack, Discord, Telegram, Microsoft Teams, and Signal on the computer where it is installed. Those sessions stay in the operating system keychain. tinyclient.io does not issue access tokens, does not accept credentials, and does not proxy messages.

## Audience

People and agents reading the public website.

## Registration

There is no registration endpoint. Do not POST credentials anywhere on tinyclient.io.

## Protected resources

None. The pages, docs, and download redirects are public. `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server` are intentionally absent: publishing an issuer or a token endpoint would point agents at a server that does not exist.

## How to use the site

Read https://tinyclient.io/llms.txt and https://tinyclient.io/docs/. Downloads are linked from the getting-started page. No bearer token is required.
3 changes: 3 additions & 0 deletions site/docs/architecture.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<meta name="referrer" content="no-referrer">
<title>Architecture · tinyclient docs</title>
<meta name="description" content="How tinyclient's processes fit together: chatd, the window, mediad and the sign-in helper, the local protocol, storage and adapters.">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://tinyclient.io/docs/architecture.html">
<link rel="ai-catalog" href="/.well-known/ai-catalog.json">
<meta name="color-scheme" content="dark light">
<meta name="theme-color" content="#121315" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
Expand Down
3 changes: 3 additions & 0 deletions site/docs/build.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<meta name="referrer" content="no-referrer">
<title>Build from source · tinyclient docs</title>
<meta name="description" content="Build tinyclient from source: prerequisites, cargo commands, the Telegram app id, the Signal feature and the demo mode.">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://tinyclient.io/docs/build.html">
<link rel="ai-catalog" href="/.well-known/ai-catalog.json">
<meta name="color-scheme" content="dark light">
<meta name="theme-color" content="#121315" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
Expand Down
Loading
Loading