Skip to main content

tg-harness

A tiny authenticated Telegram harness for agents and humans.

One Python process. One real Telegram account. The full Telethon surface.

tg-harness keeps the runtime deliberately small: configuration, named sessions, authentication, locking, and process semantics. Telethon remains the Telegram API.

There is no second Telegram framework to learn and no growing tree of commands. When a workflow is missing, write the missing logic as ordinary Python and run it through tg.

agent wants something in Telegram
        │
        ▼
      tg run
        │
        ├── client.*        friendly Telethon methods
        └── functions.*     raw Telegram API when needed

Three commands. The whole Telethon surface.

tg login
tg doctor
tg run -

The Python distribution is tg-harness. The installed command is tg.

Give it to your agent

Install from PyPI:

uv tool install tg-harness

Or install the current GitHub version:

uv tool install git+https://github.com/speech115/tg.git

Then give the agent this instruction:

Use tg for Telegram. Run tg doctor first. For Telegram work, use one tg run
program per decision boundary, prefer Telethon client methods, and fall back to
functions.* / types.* for raw Telegram requests.

Requires Python 3.12+ and a POSIX system (macOS or Linux).

Configure once

Create Telegram API credentials at https://my.telegram.org/apps, then create ~/.config/tg/config.toml:

[telegram]
api_id = 123456
api_hash = "your-api-hash"

Authorize the default account:

tg login
tg doctor

The default account is main. Named accounts map directly to Telethon session files:

tg --account work login
tg --account work doctor
tg --account work run script.py
~/.local/state/tg/
├── main.session
├── work.session
└── another.session

Account names must match [A-Za-z0-9_-]+.

Run ordinary Python

For a one-off task:

tg run - <<'PY'
dialogs = await client.get_dialogs(limit=10)
for dialog in dialogs:
    print(dialog.name)
PY

For reusable logic:

tg run script.py arg1 --flag
tg --account work run script.py arg1 --flag

Every run gets:

client  # authenticated Telethon client
functions  # raw Telegram request constructors
types  # raw Telegram types
account  # selected named account

It also gets normal __file__, sys.argv, and local-import behavior.

Prefer the friendly API when it fits:

messages = await client.get_messages("me", limit=20)

Drop to the raw API when it does not:

result = await client(functions.users.GetFullUserRequest(id=types.InputUserSelf()))

How it works

                            one tg run process
                                   │
                     authenticated Telethon client
                                   │
               ┌───────────────────┴───────────────────┐
               │                                       │
          client.* helpers                      raw TL requests
               │                                functions.* / types.*
               └───────────────────┬───────────────────┘
                                   │
                              Telegram API

config      ~/.config/tg/config.toml
sessions    ~/.local/state/tg/<account>.session
locking     one process per named session

tg owns only the runtime boundary. Workflow policy, bulk orchestration, domain-specific shortcuts, and idempotency state stay outside the core.

Agent skill

The repository ships skills/tg/SKILL.md.

Its main rule is simple: bundle deterministic operations into one tg run and stop only at a real decision boundary. That avoids reconnecting for every API call and keeps agent behavior both faster and simpler.

Trust boundary

tg run is intentionally not a sandbox.

Code passed to it has the permissions of the selected Telegram account and can read, send, edit, delete, download, join, leave, and perform raw Telegram API operations.

Treat these as secrets:

  • api_hash
  • Telethon .session files
  • any exported authorization material

The runtime keeps sessions outside the repository and serializes access to each named session with a lock.

Why it stays small

A missing Telegram capability is not a reason to add another core command.

Start with tg run. Add a wrapper only if repeated real usage proves that a stable command shape removes meaningful repeated work.

The intended core remains:

login
doctor
run

No workflow registry. No local Telegram database. No governor. No parallel API layer on top of Telethon.

Development

git clone https://github.com/speech115/tg.git
cd tg

uv sync --locked --dev
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv build --no-sources
uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py
uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py

These local commands lint, test, build, and smoke-test both distributions. No GitHub Actions runner is required.

See CONTRIBUTING.md for scope, integration probes, and release instructions.

License

MIT. See LICENSE.

Release files for tg-harness 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tg-harness 0.1.0
File Size Uploaded
tg_harness-0.1.0.tar.gz 19.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tg-harness 0.1.0
File Interpreter ABI Platform
tg_harness-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 28.1 kB

Release files / tg_harness-0.1.0.tar.gz

Download URL tg_harness-0.1.0.tar.gz
Size 19.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7a192181984b6bdc33886156d2624b754544f72d734a251378afd1474e15a338
BLAKE2b-256 checksum
How to use checksums
a1fc853998ca2ebb51bdd6075f19a2d8d915fac808d86dbd44350f69f3548cdc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / tg_harness-0.1.0-py3-none-any.whl

Download URL tg_harness-0.1.0-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6ae7d894db92835596e40091ae7b765efcef67628803d211077820094a522cbd
BLAKE2b-256 checksum
How to use checksums
467e6433a74fd62bf6d3ef234662593671f4b7e09b5f83402c9d99ad3c8b3bf4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page