Skip to main content

Link a project directory to a Telegram bot that chats with Claude

Project description

link-project-to-chat

Chat with an LLM agent about a project via Telegram or a local web UI. Links a local directory to a chat surface — send messages, get responses with full project context. Claude is the default backend; Codex, OpenAI API, Gemini, Mistral, Grok, and Claude SDK are selectable via /backend.

Security warning

This tool exposes agent CLIs and a /run command for shell execution. It is effectively a remote shell on your machine. On Telegram, only use it with bot tokens you control and never share them. With the web UI, bind to 127.0.0.1 and don't expose the port to the public internet without your own auth in front.

On Telegram, access is restricted to configured usernames. On first contact, the bot locks each user's numeric Telegram ID — subsequent requests are validated by ID, not username, so a username change cannot bypass access. Multiple users are supported.

Requirements

  • Python 3.11+
  • Claude Code installed and authenticated (claude on PATH) for the default backend; optional Codex CLI (codex on PATH), OpenAI API key (OPENAI_API_KEY with [openai-api]), Gemini CLI, Mistral Vibe CLI, Grok Build CLI, or Claude SDK extra for alternate /backend choices
  • For Telegram: a Telegram bot token — create a bot via @BotFather on Telegram
  • For local Web UI: nothing extra beyond the [web] install extra
  • For Google Chat: the [google-chat] install extra and a readable Google service-account JSON file
  • For Slack: the [slack] install extra and a Slack app with a Bot User OAuth Token (xoxb-…) plus an App-Level Token (xapp-…) — see docs/slack-app-manifest.yaml and the "Slack transport" section below

Install

pipx install link-project-to-chat

PyPI status: verified published on PyPI 2026-06-25.

Optional extras:

Extra Pulls in Use it for
[create] httpx, telethon /create_project, /create_team, BotFather automation
[voice] openai OpenAI Whisper API voice transcription
[openai-api] openai Direct OpenAI API coding-agent backend (/backend openai)
[web] fastapi[standard], jinja2, aiosqlite Local browser UI transport
[google-chat] fastapi[standard], httpx, google-auth Google Chat HTTP endpoint transport
[slack] slack_bolt, slack_sdk Slack Socket Mode transport
[claude-sdk] claude-agent-sdk Claude SDK backend (/backend claude_sdk)
[all] all of the above, including [claude-sdk] Everything

Example:

pipx install "link-project-to-chat[all]"

Contributor / from-source install

pip install -e "."            # core only
pip install -e ".[all]"       # all optional deps
pip install -e ".[dev]"       # + ruff / mypy / pytest-cov / diff-cover (quality gate)

# Quality gate (run before every commit)
make check                    # blocking: ruff lint, mypy baseline gate, pytest

Quick start

The ad-hoc --path/--token flow bypasses the config file, so pass the one-shot allowed Telegram username on the same command line. For saved bots, prefer configure --add-user USER[:ROLE].

link-project-to-chat start --path /path/to/project --token YOUR_BOT_TOKEN --username your_telegram_username

Setup with config

# Add your Telegram username (defaults to executor role)
link-project-to-chat configure --add-user your_telegram_username

# Add a project
link-project-to-chat projects add --name myproject --path /path/to/project --token YOUR_BOT_TOKEN

# Start the bot
link-project-to-chat start

Web UI (alternative to Telegram)

Run the same project bot in your browser instead of Telegram. No bot token required.

pipx install "link-project-to-chat[web]"
link-project-to-chat start --project myproject --transport web --port 8080
# then open http://localhost:8080

The web UI is local-only — bind to 127.0.0.1 and never expose it to the public internet without your own auth in front, since the same /run shell-execution surface is exposed.

Web auth rate-limiting. POST /auth applies per-source backoff: after 5 bad tokens from the same source within a 5-minute window the endpoint returns 429 Too Many Requests with a Retry-After header; a successful login clears the counter. If the web transport is deployed behind a reverse proxy, ensure the proxy forwards the real client IP via X-Forwarded-For — without it, all clients share one counter and one bad actor can lock out everyone.

Web bootstrap token. The one-time bootstrap token is printed to stdout (operator console only) on startup; only a non-reversible sha256(token)[:12] fingerprint appears in the application log. Never paste the raw token into a log-aggregation system.

Windows secrets at rest. On Windows, config.json, the telethon session file, and per-project meta_dir receive a current-user-only, inheritance-removed DACL (via pywin32 SetNamedSecurityInfo, or an icacls fallback) at the same choke points as the POSIX 0o600/0o700 chmod. Migration: a Windows deployment where a second local account legitimately read the config must re-grant access explicitly via icacls or run under a shared service account.

Google Chat transport

Google Chat bots are managed by the existing link-project-to-chat.service manager unit. To add a Google Chat bot for a project:

  1. Create a Cloud Console Chat app on a GCP project and download the service-account JSON. One Chat app per GCP project is a Google constraint, so each project needs its own GCP project + Chat app + service-account JSON.
  2. In the manager Telegram bot, open the project view and tap [Add Google Chat]. The wizard collects the SA path, port, public URL, and slash-command ID.
  3. The wizard prints a ready-to-paste nginx vhost snippet. Deploy it on your reverse proxy, run certbot for the new subdomain, then sudo nginx -t && sudo systemctl reload nginx.
  4. From the project's Google Chat space, DM the bot. The reply arrives via the manager-spawned google_chat subprocess on the configured port.

Per-project config lives under projects.<name>.google_chat. Operational defaults (host, TTLs, byte caps, audience type) come from the top-level google_chat block. Each project still requires its own GCP project + Chat app + service-account JSON; one Chat app per GCP project is a Google constraint.

rebuild.sh restarts the manager, which in turn restarts every supervised google_chat subprocess — no separate link-project-to-chat-gchat-*.service units anymore.

Google Chat v1.2 supports text, slash commands (/lp2c ...), card buttons with HMAC-signed callbacks, thread-aware replies, attachment download (uploaded-content, capped by attachment_max_bytes), prompt dialogs with form-input submissions, and both endpoint_url and project_number audience verification modes.

Known v1.2 limitations (carried forward from the v1 design spec):

  • The HMAC secret for callback tokens is per-process (secrets.token_bytes(32) at start). Any card or prompt posted before a bot restart becomes unverifiable afterward. Re-trigger the prompt on the user's next message.
  • Prompt submissions are space-bound by default. The transport supports sender-binding via expected_sender_native_id (open_prompt keyword), but bot.py wiring to thread the originating user through is a follow-up.
  • The duplicate-event cache is in-memory only; a restart resets the seen-event set, so a Google retry that arrives across a restart could double-dispatch.
  • Native inline REQUEST_DIALOG (where the bot returns a dialog synchronously from the HTTP route) is intentionally deferred because it conflicts with the fast-ack queue model. v1.2 uses card-button + SUBMIT_DIALOG instead.
  • Outbound file and voice upload is deferred. Google Chat media.upload requires user OAuth scopes, while this transport uses service-account app auth (chat.bot); send_file and send_voice return a thread-aware text fallback instead.

Google Chat — room awareness scope requirement. For the bot to receive all space messages (not just mentions) on Google Chat spaces, the app must be configured in the Google Chat API console to receive MESSAGE events for all space messages. In the Google Cloud Console under "Google Chat API > Configuration > App features", enable "Receive 1:1 messages" AND "Join spaces and group conversations", and grant the bot the https://www.googleapis.com/auth/chat.bot scope. If the app is only set up to receive app-mention events, drive-by chatter will silently never reach the bot, and room-awareness context will be empty.

Slack transport

Slack bots are managed by the existing link-project-to-chat.service manager unit. Because Slack uses Socket Mode (an outbound persistent WebSocket), no inbound HTTPS endpoint, port, or reverse-proxy snippet is required — bots connect outward to Slack and receive events over the same socket.

One-click install via app manifest (recommended). Operators with admin rights on their workspace can paste the bundled manifest:

  1. Go to api.slack.com/appsCreate New AppFrom an app manifest.
  2. Select the target workspace, then paste the contents of docs/slack-app-manifest.yaml. Slack pre-fills name, scopes, the /lp2c slash command, Socket Mode toggle, and the event subscriptions.
  3. After creating the app, install it to the workspace ("Install to Workspace" button), accept the scope grants, and copy:
    • the Bot User OAuth Token (starts with xoxb-…) from the "OAuth & Permissions" page, and
    • an App-Level Token with connections:write scope (starts with xapp-…) from the "Basic Information" → "App-Level Tokens" section. Create one if none exists.
  4. In the manager Telegram bot, open /bots and create a new bot with transport kind Slack. The wizard asks for a JSON credential blob containing bot_token and app_token; it calls auth.test to validate the tokens, then persists the credentials and starts the supervised Slack subprocess. To tear down, open the bot detail in /bots and delete it. (The per-project /configure_slack / /remove_slack wizard was removed in v5.0.0; manage Slack bots via the manager /bots UI — manager/bot_admin.py.)
  5. From your Slack workspace, DM the bot OR @mention it in a channel where the bot is a member. Replies arrive via the manager-spawned slack subprocess over the Socket Mode connection.

Manual install walkthrough (operators who don't paste the manifest). The manifest path is preferred, but the following steps reproduce the same end state for operators who need to customize the app on the "Basic Information" / "OAuth & Permissions" / "Event Subscriptions" pages directly:

  1. Create a Slack app from scratch at api.slack.com/apps.
  2. Enable Socket Mode under "Settings → Socket Mode" and generate an App-Level Token with the connections:write scope.
  3. Under OAuth & Permissions → Scopes → Bot Token Scopes, add the following 15 scopes — these are the minimum set required by the transport per spec §9.5:
    • app_mentions:read
    • chat:write
    • channels:history
    • channels:read
    • commands
    • files:read
    • files:write
    • groups:history
    • groups:read
    • im:history
    • im:read
    • mpim:history
    • mpim:read
    • users:read
    • users.profile:read
  4. Under Event Subscriptions → Subscribe to bot events, add: message.im, app_mention, message.channels, message.groups, message.mpim, file_shared, member_joined_channel, member_left_channel. (Socket Mode does not require a Request URL.)
  5. Under Slash Commands, register /lp2c (description and usage hint are up to you).
  6. Under Interactivity & Shortcuts, enable interactivity.
  7. Install the app to the workspace and copy the bot OAuth token (xoxb-…) and app-level token (xapp-…), then follow step 4 of the manifest path above to register the credentials via the manager /bots UI. (The per-project /configure_slack wizard was removed in v5.0.0.)

Per-project config lives under projects.<name>.slack:

{
  "slack": {
    "bot_token": "xoxb-...",
    "app_token": "xapp-...",
    "workspace_id": "T0123ABCD",
    "channel_allowlist": []
  }
}

Operational defaults (channel allowlist) come from the top-level slack block when the project field is empty. channel_allowlist empty = permissive (matches Telegram); non-empty gates channel chatter + @-mentions but never DMs.

rebuild.sh restarts the manager, which in turn restarts every supervised Slack subprocess — no separate link-project-to-chat-slack-*.service units required.

The Slack transport supports:

  • DMs (message.im), addressed by-default.
  • Channel @-mentions (app_mention) — drive-by channel chatter is also dispatched when the channel is on the allowlist, mirroring the Telegram room-binding behavior (see Group support below).
  • Slash commands (/lp2c <subcommand> [args]) routed through the same handler table as Telegram /commands.
  • Modal-driven prompts for TEXT / SECRET / CHOICE / CONFIRM.
  • Block Kit formatting with **bold** / *italic* / [label](url) rewriting to Slack's mrkdwn.
  • File upload via files_upload_v2, threaded to the originating mention.
  • Threading. Outbound replies inherit thread_ts from the inbound mention; top-level replies stay top-level.
  • Identity namespacing. Identity.transport_id = "slack:T_workspace_id" so two workspaces never collide via AuthMixin.

Slack auth and identity locking. Auth flows through the immutable slack:<workspace_id>:<user_id> lock key — a workspace member cannot rename themselves to a privileged username and win the first-contact role race. Username-only AllowedUser entries (e.g. {"username": "alice", "role": "executor"}) are spoofable-until-locked: the window closes the moment Alice's first message atomically appends "slack:T0123ABCD:U9876XYZ" to her locked_identities. To eliminate the window entirely, pre-author the locked_identities field before Alice's first contact:

{"username": "alice", "role": "executor", "locked_identities": ["slack:T0123ABCD:U9876XYZ"]}

Known v1 limitations:

  • No team-mode-on-Slack relay. Cross-bot team routing on a Slack workspace is a v2 follow-up — enable_team_relay is a no-op stub for now. Single-bot DMs + channels work end-to-end.
  • Stub for chat-member events. on_chat_member_event, list_chat_admins, and get_chat_member return empty / None pending Slack member_joined_channel wiring (v2.1+).

Example session

You: what does the auth module do?
Agent: The auth module handles JWT token validation and...

You: /run npm run dev
(runs in background, check output with /tasks → Log)

You: add a test for expired token handling
Agent: I'll add a test for that. [edits file]...

You: /tasks
> #1 npm run dev
+ #2 [claude] add a test for expired token...

How it works

/run commands execute in parallel with agent work. Agent turns within a single bot session are serialized so they keep one consistent shared session and can't step on each other during interactive follow-ups.

Project bot commands

Command Description
(message) Chat with the active backend in the project context
/run <cmd> Run a shell command in the background
/tasks List tasks with per-task buttons (log, cancel)
/backend [claude|codex|gemini|mistral|grok|claude_sdk|openai] Show or switch the active backend
/model [name] Set backend model
/effort low/medium/high/xhigh/max Set backend reasoning depth
/thinking on/off Stream live reasoning when the active backend supports it
/context [on|off|N] Show or set per-chat conversation history depth
/permissions <mode> Set backend permission / sandbox mode
/skills List available skills
/use [skill] Activate a skill (system prompt) — or pick from list
/stop_skill Deactivate current skill
/create_skill <name> Create a new skill (project or global)
/delete_skill <name> Delete a skill
/persona [name] Activate a persona (per-message) — or pick from list
/stop_persona Deactivate current persona
/create_persona <name> Create a new persona (project or global)
/delete_persona <name> Delete a persona
/voice Show voice transcription status
/compact Compress session context
/reset Clear the active backend session
/status Show bot and backend status
/version Show version
/help Show available commands

Claude remains the default backend. Codex is opt-in via /backend codex and supports /model, /effort, /permissions, session resume, and token usage reporting in /status. Some commands stay capability-gated: for example, live /thinking, /compact, skills, personas, and allowed/disallowed tool lists are Claude-only unless another backend exposes equivalent support.

Backends

  • Mistral (/backend mistral, opt-in)

    • Install the Vibe CLI: curl -LsSf https://mistral.ai/vibe/install.sh | bash
    • Set MISTRAL_API_KEY in the bot's process env.
    • Default model: mistral-medium-3.5. Other models: devstral-small, codestral-25.11 (paid), mistral-large-latest (paid).
    • Permission/effort presets map to Vibe's --agent flag (default, plan, accept-edits, auto-approve).
    • Session continuity via Vibe's -c flag with VIBE_HOME isolation (per-project).
    • Known limitation (v2.0.8): /model <id> updates in-memory state but does NOT auto-persist to <project>/.vibe/config.toml — operators must call MistralBackend.set_model() directly OR edit ~/.vibe/config.toml manually. Fix planned for v2.0.9.
  • Grok Build (/backend grok, opt-in)

    • Install the Grok Build CLI: curl -fsSL https://x.ai/cli/install.sh | bash
    • Set GROK_CODE_XAI_API_KEY in the bot's process env (xAI Console → API Keys). Falls back to interactive OAuth creds in ~/.grok/auth.json if the env var is unset.
    • Model: grok-build (stable channel). grok models may list grok-build-latest too, but access depends on your xAI team's ACL — the picker ships only the universally-available stable slug.
    • /permissions accepts Claude-shape values verbatim: default, acceptEdits, auto, dontAsk, bypassPermissions, plan. (dangerously-skip-permissions aliases to bypassPermissions.)
    • /effort accepts low, medium, high, max and is a SEPARATE orthogonal flag (--reasoning-effort) — it does NOT override /permissions.
    • Session continuity via stream-captured session_id + --resume <id> per chat.

Skills

Skills are markdown files passed as system prompt via --append-system-prompt. Claude sees them as background context, like Claude Code's native skill handling. A skill and a persona can be active at the same time.

Locations (highest priority first):

  1. Per-project: <project_path>/.claude/skills/<name>.md
  2. App global: ~/.link-project-to-chat/skills/<name>.md
  3. Claude Code user skills: ~/.claude/skills/<name>.md (including <name>/SKILL.md directories)

Higher-priority skills override lower ones with the same name. Claude Code user skills are automatically available.

Use /create_skill <name> to create — you'll choose project or global scope, then send the content. /use without arguments shows an inline picker.

Personas

Personas are markdown files prepended to every message. Claude sees them with each message, good for enforcing a specific voice or role.

Locations (highest priority first):

  1. Per-project: <project_path>/.claude/personas/<name>.md
  2. App global: ~/.link-project-to-chat/personas/<name>.md

Use /create_persona <name> to create. /persona without arguments shows an inline picker.

Example: Create ~/.link-project-to-chat/personas/reviewer.md:

You are a senior code reviewer. Focus on bugs, security issues,
and performance problems. Be direct and concise.

Then /persona reviewer to activate.

Voice messages

Send voice messages in Telegram and they'll be transcribed and sent to the active backend as text.

Setup with OpenAI Whisper API (recommended)

link-project-to-chat setup --stt-backend whisper-api --openai-api-key YOUR_KEY

Setup with local whisper.cpp

Requires whisper.cpp and ffmpeg installed:

link-project-to-chat setup --stt-backend whisper-cli --whisper-model base

Language hint

For better accuracy with non-English audio:

link-project-to-chat setup --whisper-language ka

Install with voice extra

pipx install "link-project-to-chat[voice]"

Multi-user support

Multiple Telegram users can access the same bot:

# Add users (defaults to executor; append `:viewer` for read-only role)
link-project-to-chat configure --add-user alice
link-project-to-chat configure --add-user bob:viewer

# Remove a user
link-project-to-chat configure --remove-user bob

Users can also be managed from the Manager Bot via /add_user and /remove_user.

Plugins

Plugins extend the project bot with custom commands, message handlers, task hooks, button handlers, and Claude prompt context. They are external Python packages discovered via the lptc.plugins entry point group, and they're transport-portable: the same plugin works on Telegram, on the Web UI, and on any future Discord/Slack/Google Chat transport.

Activating plugins for a project

Add them to the project's config entry:

{
  "projects": {
    "myproject": {
      "path": "/path/to/project",
      "telegram_bot_token": "...",
      "plugins": [
        {"name": "in-app-web-server"},
        {"name": "diff-reviewer"}
      ]
    }
  }
}

Or toggle them in the manager bot: open a project → Plugins → tap a plugin. Restart the bot after changes.

Writing a plugin

from link_project_to_chat.plugin import Plugin, BotCommand


class MyPlugin(Plugin):
    name = "my-plugin"
    depends_on = []

    async def start(self):
        ...

    async def stop(self):
        ...

    async def on_message(self, msg):
        # msg is an IncomingMessage — text, sender, chat, files all available
        return False  # True consumes; the agent (Claude/Codex) is skipped

    def get_context(self):
        # Only used when the active backend is Claude; ignored for Codex/Gemini.
        return "Extra system-prompt context"

    def commands(self):
        async def hello(invocation):
            # invocation is a CommandInvocation
            await self._ctx.transport.send_text(invocation.chat, "hi")
        return [BotCommand(command="hello", description="say hi", handler=hello)]

Expose it via your plugin package's pyproject.toml:

[project.entry-points."lptc.plugins"]
my-plugin = "my_package:MyPlugin"

Role-based access

Set allowed_users on a project to enable per-user roles:

"allowed_users": [
  {"username": "alice", "role": "executor"},
  {"username": "bob", "role": "viewer"}
]

Viewers can use /tasks (including the per-task Log button it surfaces), /status, /help, /version, /skills (listing), /context (display), and any plugin command flagged viewer_ok. Executors have the full command set. allowed_users is the sole auth source — an empty list means no one is authorized (fail-closed). The first request from each user atomically appends their identity to locked_identities and writes it back to the config so subsequent requests validate by native ID rather than username (preserves the username-spoof protection from the pre-v1.0 model).

Group support (Telegram)

As of v7.0.0, a bot answers in a Telegram room if and only if that room is bound to it — a ChatBinding with chat_kind="room" stored in the bot config. Binding is the opt-in; unbinding is the off-switch. The legacy respond_in_groups per-project flag was retired v7.0.0 and is silently dropped on next config save.

To bind a Telegram group to a bot, use the manager /bots UI: open the bot detail, tap Bind room, and forward any message from the target group. The bot will start answering in that room immediately — no restart required.

When a room is bound:

  • The bot responds when @<bot_username> appears in a group message OR the message replies to one of the bot's prior messages.
  • All other group messages are silently ignored — no auth check runs, nothing is logged.
  • The @<bot_username> mention is stripped from the prompt before the agent sees it.
  • /commands work in groups with the same role gate as DM. Telegram routes /cmd@MyBot only to the addressed bot in multi-bot rooms.
  • The bot ignores all other bots' messages, including @<bot_username> from a peer bot (loop defense).

Safety prompt (v1.2.0+)

By default, the bot is given a guardrail asking it to describe-and-ask before destructive action — no kills, no restarts, no rm without your explicit go-ahead in the current message. This is the same guardrail that ships with the GitLab fork.

Per-project knobs (projects edit <name> safety_prompt <value>):

Value Effect
default (or omit the key) Use the built-in guardrail
Empty string "" Disable safety entirely — agent acts without asking
Any other string Replace the built-in with your custom text

CLI: link-project-to-chat projects add ... --safety-prompt "...". Manager bot: the field appears in the project-edit text wizard.

Hot-reload of allowed_users (v1.2.0+)

When you /add_user, /remove_user, /promote_user, or /demote_user from the manager bot, the running project bots pick up the change within 5 s — no restart required. The reload is debounced and clobber-safe; it never wipes in-flight first-contact identity locks. Other config fields (token, model) still need a bot restart to apply.

Inter-message group context (v1.2.0+)

When the bot answers in a bound room, it sees the chatter that happened between its previous LLM call and the current one — [Recent discussion] is prepended to the prompt. Buffer is per-chat, 200-message ring, in-memory only (cleared on restart). Works for any room-kind chat across transports. The current local Web UI is direct browser-to-bot chat and emits DM-kind messages, so room context does not apply there until a multi-user Web room model exists.

meta_dir config field (v1.2.0+)

Top-level meta_dir in config.json redirects per-bot / per-plugin storage to a different filesystem volume. Default is ~/.link-project-to-chat/meta/. Operators upgrading to v1.2.0+ don't need to migrate existing data; the field is only consulted when bots start.

Open-core extension contracts (v9.4.0)

The 9.4.0 release ships the additive open-core Stage A contract foundation: the public surfaces third-party extensions and future hosted tooling build against. It is purely additive — every existing adapter and command keeps working unchanged.

Extension descriptors & conformance kits

Extensions are declared as versioned descriptors in link_project_to_chat.extensions and discovered through three entry-point groups: lptc.plugins, lptc.backends, and lptc.transports. Discovery is metadata-only — an extension your configuration doesn't select is never imported — and descriptor validation or selection failures close loudly (DescriptorError, DuplicateExtensionError) rather than falling back to a silent guess.

Extension authors can run the same contract suites the first-party adapters pass via the importable conformance kits in link_project_to_chat.testing:

# In your extension's test module:
from link_project_to_chat.testing.transport_contract import (
    make_transport_contract_tests,
)

globals().update(make_transport_contract_tests({"my_transport": MyTransport}))

Equivalent factories exist for backends (make_backend_contract_tests) and plugins (make_plugin_contract_tests). The kits never import a private adapter — all adapter-specific construction stays in the consuming repository.

Generic transport options

BotConfig.transport_options is a generic per-transport options dict, so new transports no longer need a dedicated config field for every knob. Existing flat credential blocks (slack, google_chat, web, Telegram token fields) keep loading byte-for-byte via legacy dual-read.

Atomic onboarding

link-project-to-chat init, link-project-to-chat link, and link-project-to-chat projects add share one core-owned atomic registration API (config/registration.py): validate, mutate, then fsync-replace config.json in one step — concurrent or interrupted onboarding can't leave a torn config.

Config schema-version refusal

Every config.json writer checks the on-disk schema_version before mutating and refuses to write a file stamped with a future or invalid version (produced by a newer build or hand edit), instead of silently downgrading it. Successful writes stamp the supported version.

Per-bot SQLite versioning

Each bot's SQLite database carries a PRAGMA user_version schema marker. The build understands the newest SQLITE_SCHEMA_VERSION and applies N/N-1 migrations atomically (migration + version stamp in one transaction); a database stamped by a newer build refuses to open instead of risking schema drift.

Dormant bot supervision

link_project_to_chat.supervision defines a public BotSupervisor protocol keyed strictly by bot name — team processes, the relay service, and project workloads are deliberately out of its namespace. The manager's bot lifecycle flows through this seam (ProcessManager accepts an injected supervisor; the default is subprocess-based). Stage A ships a dormant SystemdBotSupervisor implementation plus an inert systemd unit template for operator opt-in only: nothing installs, enables, or starts any unit. The template is package data inside the wheel — locate it via importlib.resources.files("link_project_to_chat") / "packaging" / "systemd" / "link-project-to-chat-bot@.service" (in a source checkout: src/link_project_to_chat/packaging/systemd/).

Manager bot

The manager bot controls multiple project bots from a single Telegram chat — start, stop, view logs, add/remove projects, and create new projects automatically.

Manager-started bots inherit the active --config path, so custom config files work consistently for project bots, team bots, personas, and team relay bootstrap.

Setup

link-project-to-chat configure --add-user your_telegram_username --manager-token MANAGER_TOKEN
link-project-to-chat start-manager

Manager commands

Command Description
/projects List projects and project actions
/bots List bots and their chat/project bindings
/backends [backend] Show backend install/version/auth status and setup actions
/start_all Start all projects
/stop_all Stop all projects
/create_project Create a project (GitHub or GitLab repo + auto bot creation)
/create_team Create a multi-bot team in a group chat (interactive wizard; picks GitHub or GitLab)
/delete_team Delete a team and clean up its bots
/setup Configure GitHub PAT, GitLab PAT/host, and Telegram API credentials
/users List authorized users
/add_user <username> Add an authorized user
/remove_user <username> Remove an authorized user
/models [backend] List the model catalog
/version Show version
/help Show commands

Automated project creation

The /create_project command automates the full project setup:

  1. Pick a repo provider (GitHub or GitLab)
  2. Select a repo (browse your repos or paste a URL)
  3. Automatically create a Telegram bot via BotFather
  4. Clone the repo
  5. Configure everything

/create_team opens with the same GitHub / GitLab picker before the browse + paste-URL + clone steps. Self-hosted GitLab is supported via Config.gitlab_host (default gitlab.com).

When you later remove a project from the manager, manager-created projects are cleaned up on a best-effort basis: the manager will try to stop the bot, delete the owned repo checkout, and revoke the owned bot via BotFather. If the project path was changed after creation, the repo is left in place intentionally.

Requirements:

  • gh CLI (optional, for GitHub repo browse/clone in the manager wizard) — gh auth login. Alternatively set a GitHub PAT via /setup.
  • glab CLI (optional, for GitLab repo browse/clone) — glab auth login. For a self-hosted GitLab instance, also set gitlab_host in your config: link-project-to-chat configure --gitlab-host gitlab.example.com (bare hostname, no scheme). Alternatively set a GitLab PAT via link-project-to-chat configure --gitlab-pat ... or /setup.
  • BotFather automation: Telegram API credentials + Telethon session
  • Team creation/deletion automation: install the optional create extras with pipx install "link-project-to-chat[create]"

Setup credentials:

# Interactive setup (recommended)
link-project-to-chat setup

# Or via the Manager Bot
/setup

The CLI setup command handles Telethon phone authentication interactively — this cannot be done through the bot due to Telegram security restrictions.

CLI reference

link-project-to-chat configure --add-user USER[:ROLE] [--remove-user USER]
                                [--reset-user-identity USER[:TRANSPORT]]
                                [--manager-token TOKEN]
                                [--gitlab-pat PAT] [--gitlab-host HOST]
link-project-to-chat setup [--github-pat PAT] [--gitlab-pat PAT] [--gitlab-host HOST]
                            [--telegram-api-id ID] [--telegram-api-hash HASH] [--phone PHONE]

link-project-to-chat projects list
link-project-to-chat projects add --name NAME --path PATH --token TOKEN [--username USER] [--model MODEL]
link-project-to-chat projects remove <name>
link-project-to-chat projects edit <name> <field> <value>

link-project-to-chat start [--project NAME] [--path PATH --token TOKEN] [--username USER] [--model MODEL]
                            [--permission-mode MODE] [--dangerously-skip-permissions]
                            [--allowed-tools TOOLS] [--disallowed-tools TOOLS]
                            [--transport telegram|web|google_chat] [--port PORT]
                            [--google-chat-host HOST] [--google-chat-port PORT]
                            [--google-chat-public-url URL]
link-project-to-chat start-manager

Config is stored at ~/.link-project-to-chat/config.json.

Deployment

Run as a systemd service

sudo tee /etc/systemd/system/link-project-to-chat.service << 'EOF'
[Unit]
Description=Link Project to Chat Manager Bot
After=network.target

[Service]
User=botuser
ExecStart=/home/botuser/.local/bin/link-project-to-chat start-manager
Restart=always
RestartSec=5
Environment=HOME=/home/botuser

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable --now link-project-to-chat

History retention & maintenance

By default, no chat history is ever deleted — the database grows unboundedly. To enable automatic pruning, set history_retention_days on a project in config.json:

{
  "projects": {
    "myproject": {
      "history_retention_days": 90
    }
  }
}

When history_retention_days is a positive integer the bot starts an opt-in maintenance scheduler (default cadence: once every 24 hours, off-peak). Each run executes three steps in order:

  1. prune — delete rows older than history_retention_days from the chat history and conversation-log stores.
  2. checkpoint — truncate the SQLite WAL file (cheap; frees disk without rewriting the database).
  3. vacuum (optional) — run VACUUM to compact the database file and return disk space to the OS. Disabled by default because it rewrites the whole file; enable by also setting "history_vacuum_on_maintenance": true.

Caution: Setting history_retention_days too low permanently loses messages older than the threshold — the prune is irreversible. Choose a value that balances storage costs against how far back you need anaphoric context ("can you do it again?") to reach.

Back up and restore bot databases

Use SQLite's sqlite3.Connection.backup online backup API while a bot is running:

import sqlite3

with sqlite3.connect("bot.db") as source, sqlite3.connect("bot.backup.db") as target:
    source.backup(target)

Do not copy only a live .db file: committed data may still be in its -wal sidecar. Stop every bot that uses the database before restoring, copy the backup into the configured database path, then verify PRAGMA integrity_check and PRAGMA user_version before restarting.

Roadmap

Shipped:

  • Telegram transport
  • Local Web UI transport (FastAPI + SSE + SQLite)
  • Voice messages (Whisper API or local whisper.cpp)
  • Multi-project manager bot, team chats, group/team relay
  • Skills and personas
  • Pluggable agent backend with /backend command and capability-gated commands. Seven backends ship: Claude (default), Codex, OpenAI API, Gemini, Mistral, Grok (added v7.x), and Claude SDK (opt-in via /backend claude_sdk when the claude-sdk extra is installed); all selectable via /backend.
  • Provider-aware /status, /context cross-backend conversation history, Codex /model, Codex /effort, and Codex /permissions.
  • Slack transport (Socket Mode, v2.0.22)
  • Google Chat transport (service-account app auth, spec #4)

Planned (designed, not yet implemented):

  • Discord transport

Contributions welcome.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

link_project_to_chat-9.4.1.tar.gz (3.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

link_project_to_chat-9.4.1-py3-none-any.whl (587.3 kB view details)

Uploaded Python 3

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page