aipager 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aipager-0.2.0/.env.example +15 -0
- aipager-0.2.0/.github/workflows/publish.yml +19 -0
- aipager-0.2.0/.github/workflows/test.yml +23 -0
- aipager-0.2.0/.gitignore +15 -0
- aipager-0.2.0/CHANGELOG.md +45 -0
- aipager-0.2.0/CONTRIBUTING.md +95 -0
- aipager-0.2.0/LICENSE +21 -0
- aipager-0.2.0/PKG-INFO +116 -0
- aipager-0.2.0/README.md +84 -0
- aipager-0.2.0/aipager/OBSERVERS.md +44 -0
- aipager-0.2.0/aipager/__init__.py +8 -0
- aipager-0.2.0/aipager/__main__.py +6 -0
- aipager-0.2.0/aipager/_dtach_redraw.py +95 -0
- aipager-0.2.0/aipager/cli.py +114 -0
- aipager-0.2.0/aipager/config.py +131 -0
- aipager-0.2.0/aipager/dtach_inject.py +211 -0
- aipager-0.2.0/aipager/dtach_launcher.py +134 -0
- aipager-0.2.0/aipager/hook_receiver.py +550 -0
- aipager-0.2.0/aipager/md_to_tg.py +73 -0
- aipager-0.2.0/aipager/notify_hook.py +72 -0
- aipager-0.2.0/aipager/observer.py +74 -0
- aipager-0.2.0/aipager/session_monitor.py +88 -0
- aipager-0.2.0/aipager/setup_wizard.py +212 -0
- aipager-0.2.0/aipager/state.py +296 -0
- aipager-0.2.0/aipager/statusline_notify.py +71 -0
- aipager-0.2.0/aipager/telegram_bot.py +2242 -0
- aipager-0.2.0/aipager/transcript.py +122 -0
- aipager-0.2.0/pyproject.toml +54 -0
- aipager-0.2.0/scripts/aipager.service.example +23 -0
- aipager-0.2.0/tests/__init__.py +0 -0
- aipager-0.2.0/tests/conftest.py +11 -0
- aipager-0.2.0/tests/test_config_load.py +72 -0
- aipager-0.2.0/tests/test_md_to_tg.py +52 -0
- aipager-0.2.0/tests/test_state.py +70 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Required
|
|
2
|
+
CLAUDE_TG_BOT_TOKEN=your-bot-token-here
|
|
3
|
+
CLAUDE_TG_CHAT_ID=your-chat-id-here
|
|
4
|
+
|
|
5
|
+
# Optional: working directory for Claude Code sessions (default: cwd)
|
|
6
|
+
# AIPAGER_WORK_DIR=/path/to/your/project
|
|
7
|
+
|
|
8
|
+
# Optional: observer bot tokens (read-only mirrors)
|
|
9
|
+
# OBSERVER_BOTS=token1:chatid1,token2:chatid2
|
|
10
|
+
|
|
11
|
+
# Optional: rich markdown→HTML summaries (default: 1)
|
|
12
|
+
# CLAUDE_RICH_SUMMARIES=1
|
|
13
|
+
|
|
14
|
+
# Optional: stale busy alert threshold in seconds (default: 1200 = 20min)
|
|
15
|
+
# STALE_BUSY_TIMEOUT=1200
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ['v*']
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: '3.12'
|
|
17
|
+
- run: pip install build
|
|
18
|
+
- run: python -m build
|
|
19
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: pytest py${{ matrix.python }}
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python: ['3.10', '3.11', '3.12', '3.13']
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python }}
|
|
21
|
+
- run: pip install -e '.[dev]'
|
|
22
|
+
- run: ruff check aipager tests
|
|
23
|
+
- run: pytest -q
|
aipager-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-05-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Depend on [`dtach-bin`](https://pypi.org/project/dtach-bin/) so
|
|
14
|
+
`pipx install aipager` pulls in a precompiled `dtach` binary for
|
|
15
|
+
Linux x86_64/aarch64 and macOS x86_64/arm64. No manual system
|
|
16
|
+
package install needed.
|
|
17
|
+
- GitHub Actions workflows: `test.yml` (ruff + pytest on Python
|
|
18
|
+
3.10–3.13) and `publish.yml` (build + Trusted Publisher OIDC upload
|
|
19
|
+
on tag push).
|
|
20
|
+
- `CONTRIBUTING.md` documenting the local dev setup and release flow.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- README leads with `pipx install aipager` as the primary install
|
|
24
|
+
path; `pip install -e .` demoted to a "Developing locally" section.
|
|
25
|
+
|
|
26
|
+
## [0.1.0] - 2026-05-16
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- `pyproject.toml` with hatchling backend
|
|
30
|
+
- MIT license
|
|
31
|
+
- README and Changelog
|
|
32
|
+
- Console script entry points: `aipager`, `aipager-hook`,
|
|
33
|
+
`aipager-statusline`, `claude-dtach`
|
|
34
|
+
- `aipager` CLI with `start`, `config`, `version` subcommands
|
|
35
|
+
- `aipager config` — interactive setup wizard that patches
|
|
36
|
+
`~/.claude/settings.json` and writes `~/.config/aipager/config.env`
|
|
37
|
+
- XDG-compliant config path (`~/.config/aipager/config.env`) with cwd
|
|
38
|
+
`.env` fallback
|
|
39
|
+
- Pure-Python port of the `claude-dtach` session launcher
|
|
40
|
+
- Test suite for state machine, markdown→HTML converter, and config loader
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- Removed hardcoded transcript directory path that worked on only one
|
|
44
|
+
machine; transcript discovery now scans all project subdirs under
|
|
45
|
+
`~/.claude/projects/`.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Contributing to aipager
|
|
2
|
+
|
|
3
|
+
## Local development
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
git clone https://github.com/dev-aly3n/aipager && cd aipager
|
|
7
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
8
|
+
pip install -e '.[dev]'
|
|
9
|
+
pytest -q
|
|
10
|
+
ruff check aipager tests
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Running the daemon during development
|
|
14
|
+
|
|
15
|
+
After `pip install -e .`, four console scripts are on your PATH:
|
|
16
|
+
|
|
17
|
+
| Script | What it does |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `aipager` | the CLI dispatcher (`start`, `config`, `version`) |
|
|
20
|
+
| `aipager-hook` | Claude Code hook handler — invoked by Claude per event |
|
|
21
|
+
| `aipager-statusline` | Claude Code statusLine — invoked on every tick |
|
|
22
|
+
| `claude-dtach` | launches a Claude Code session under `dtach` |
|
|
23
|
+
|
|
24
|
+
Tweak code, then `aipager start` runs the daemon with your changes
|
|
25
|
+
(editable install means no reinstall needed for `.py` edits).
|
|
26
|
+
|
|
27
|
+
### `dtach` during development
|
|
28
|
+
|
|
29
|
+
`dtach-bin` is a runtime dependency. If it's not yet on PyPI (or you're
|
|
30
|
+
testing changes to it), install from a local checkout:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pip install /path/to/dtach-bin
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Otherwise `pip install -e '.[dev]'` will pull the published version
|
|
37
|
+
from PyPI.
|
|
38
|
+
|
|
39
|
+
## Release process
|
|
40
|
+
|
|
41
|
+
Releases are tag-driven. Tagging a commit on `main` triggers
|
|
42
|
+
`.github/workflows/publish.yml`, which builds `sdist` + `wheel` and
|
|
43
|
+
uploads via PyPI Trusted Publisher (OIDC — no stored API token).
|
|
44
|
+
|
|
45
|
+
### Cutting a release
|
|
46
|
+
|
|
47
|
+
1. Bump `version` in `pyproject.toml`
|
|
48
|
+
2. Add a `[X.Y.Z]` section at the top of `CHANGELOG.md`
|
|
49
|
+
3. Commit: `git commit -m "bump version to X.Y.Z"`
|
|
50
|
+
4. Tag: `git tag vX.Y.Z && git push origin main --tags`
|
|
51
|
+
5. CI builds and publishes within ~2 minutes
|
|
52
|
+
|
|
53
|
+
### First-time PyPI Trusted Publisher setup (one-time)
|
|
54
|
+
|
|
55
|
+
Before the first OIDC release, you need:
|
|
56
|
+
|
|
57
|
+
1. A PyPI account at https://pypi.org/account/register/
|
|
58
|
+
2. The first upload done manually with an API token:
|
|
59
|
+
```sh
|
|
60
|
+
pip install build twine
|
|
61
|
+
python -m build
|
|
62
|
+
twine upload dist/*
|
|
63
|
+
```
|
|
64
|
+
3. Register a Trusted Publisher on PyPI for the project:
|
|
65
|
+
- Project settings → Publishing → Add a trusted publisher
|
|
66
|
+
- Provider: GitHub Actions
|
|
67
|
+
- Owner: `dev-aly3n`
|
|
68
|
+
- Repository: `aipager`
|
|
69
|
+
- Workflow file: `publish.yml`
|
|
70
|
+
- Environment: (leave blank)
|
|
71
|
+
|
|
72
|
+
After this, all future releases use OIDC. No tokens are stored anywhere.
|
|
73
|
+
|
|
74
|
+
## Style / linting
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
ruff check aipager tests
|
|
78
|
+
ruff format aipager tests # if you want auto-formatting
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The CI matrix runs Python 3.10 through 3.13 — keep the codebase free of
|
|
82
|
+
3.11+ syntax (no `Self`, no `TypeVarTuple` etc.). Use
|
|
83
|
+
`from __future__ import annotations` for new files that need modern
|
|
84
|
+
typing.
|
|
85
|
+
|
|
86
|
+
## Commit style
|
|
87
|
+
|
|
88
|
+
One-liner subject, lowercase imperative mood, ≤72 chars. No commit
|
|
89
|
+
body. Examples:
|
|
90
|
+
|
|
91
|
+
- `fix transcript path scan to handle multi-cwd setups`
|
|
92
|
+
- `add aipager service subcommand for systemd-user installer`
|
|
93
|
+
|
|
94
|
+
Squash-merge PRs that have noisy intermediate commits — the main branch
|
|
95
|
+
log should be a clean reading order.
|
aipager-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dev-aly3n
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
aipager-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aipager
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Telegram remote-control daemon for Claude Code CLI sessions running in dtach
|
|
5
|
+
Project-URL: Source, https://github.com/dev-aly3n/aipager
|
|
6
|
+
Project-URL: Issues, https://github.com/dev-aly3n/aipager/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/dev-aly3n/aipager/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: dev-aly3n <66creation@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: claude,claude-code,dtach,remote-control,telegram,tui
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Communications :: Chat
|
|
22
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: dtach-bin>=0.9
|
|
25
|
+
Requires-Dist: python-telegram-bot>=20
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
30
|
+
Requires-Dist: twine; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# aipager
|
|
34
|
+
|
|
35
|
+
Telegram remote-control for [Claude Code](https://claude.com/claude-code)
|
|
36
|
+
CLI sessions. Run Claude inside a detached terminal (`dtach`), drive it
|
|
37
|
+
from your phone — read responses, send prompts, approve permission
|
|
38
|
+
requests, switch sessions — without an SSH session staying open.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
Requires Python 3.10+. **`dtach` is installed automatically** via the
|
|
43
|
+
[`dtach-bin`](https://pypi.org/project/dtach-bin/) dependency — no
|
|
44
|
+
separate system package needed.
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
pipx install aipager
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
(or `uv tool install aipager`, or `pip install aipager` into a venv —
|
|
51
|
+
all work the same.)
|
|
52
|
+
|
|
53
|
+
Linux ARM and macOS users on Apple Silicon get the same one-command
|
|
54
|
+
install; the dtach binary ships as pre-built wheels for each platform.
|
|
55
|
+
|
|
56
|
+
> **Homebrew support is coming in v0.3** as `brew install <user>/tap/aipager`,
|
|
57
|
+
> which uses the system `dtach` instead of the bundled one.
|
|
58
|
+
|
|
59
|
+
## Configure
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
aipager config
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Interactive wizard — asks for your Telegram bot token (from
|
|
66
|
+
[@BotFather](https://t.me/BotFather)) and chat ID, validates them, then
|
|
67
|
+
patches `~/.claude/settings.json` to wire the necessary hooks
|
|
68
|
+
automatically. You never edit any file by hand.
|
|
69
|
+
|
|
70
|
+
## Run
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
aipager start
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The daemon stays in the foreground. Launch a Claude session in another
|
|
77
|
+
terminal:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
claude-dtach dev
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The daemon discovers the session within seconds and Telegram starts
|
|
84
|
+
mirroring it. To survive logout, use `screen`, `tmux`, or a systemd-user
|
|
85
|
+
unit (template at `scripts/aipager.service.example` — full `aipager
|
|
86
|
+
service install` automation lands in v0.4).
|
|
87
|
+
|
|
88
|
+
## What it does
|
|
89
|
+
|
|
90
|
+
- Mirrors Claude Code session state to Telegram: busy/idle, tool calls,
|
|
91
|
+
context %, cost, line counts
|
|
92
|
+
- Lets you reply to messages to inject prompts back into the session
|
|
93
|
+
- Surfaces permission prompts and `AskUserQuestion` dialogs as Telegram
|
|
94
|
+
inline keyboards
|
|
95
|
+
- Notifies on context warnings, compaction, session end, and stalls
|
|
96
|
+
- Supports multiple concurrent sessions with one bot
|
|
97
|
+
- Optional read-only observer bots
|
|
98
|
+
|
|
99
|
+
## Developing locally
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
git clone <repo-url> aipager && cd aipager
|
|
103
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
104
|
+
pip install -e '.[dev]'
|
|
105
|
+
pytest -q
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
When iterating on code changes you'll generally want to also install
|
|
109
|
+
`dtach-bin` from a local checkout — or `pip install dtach-bin` — so the
|
|
110
|
+
runtime can find `dtach` on PATH.
|
|
111
|
+
|
|
112
|
+
Release process is in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT — see [LICENSE](LICENSE).
|
aipager-0.2.0/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# aipager
|
|
2
|
+
|
|
3
|
+
Telegram remote-control for [Claude Code](https://claude.com/claude-code)
|
|
4
|
+
CLI sessions. Run Claude inside a detached terminal (`dtach`), drive it
|
|
5
|
+
from your phone — read responses, send prompts, approve permission
|
|
6
|
+
requests, switch sessions — without an SSH session staying open.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Requires Python 3.10+. **`dtach` is installed automatically** via the
|
|
11
|
+
[`dtach-bin`](https://pypi.org/project/dtach-bin/) dependency — no
|
|
12
|
+
separate system package needed.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pipx install aipager
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
(or `uv tool install aipager`, or `pip install aipager` into a venv —
|
|
19
|
+
all work the same.)
|
|
20
|
+
|
|
21
|
+
Linux ARM and macOS users on Apple Silicon get the same one-command
|
|
22
|
+
install; the dtach binary ships as pre-built wheels for each platform.
|
|
23
|
+
|
|
24
|
+
> **Homebrew support is coming in v0.3** as `brew install <user>/tap/aipager`,
|
|
25
|
+
> which uses the system `dtach` instead of the bundled one.
|
|
26
|
+
|
|
27
|
+
## Configure
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
aipager config
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Interactive wizard — asks for your Telegram bot token (from
|
|
34
|
+
[@BotFather](https://t.me/BotFather)) and chat ID, validates them, then
|
|
35
|
+
patches `~/.claude/settings.json` to wire the necessary hooks
|
|
36
|
+
automatically. You never edit any file by hand.
|
|
37
|
+
|
|
38
|
+
## Run
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
aipager start
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The daemon stays in the foreground. Launch a Claude session in another
|
|
45
|
+
terminal:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
claude-dtach dev
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The daemon discovers the session within seconds and Telegram starts
|
|
52
|
+
mirroring it. To survive logout, use `screen`, `tmux`, or a systemd-user
|
|
53
|
+
unit (template at `scripts/aipager.service.example` — full `aipager
|
|
54
|
+
service install` automation lands in v0.4).
|
|
55
|
+
|
|
56
|
+
## What it does
|
|
57
|
+
|
|
58
|
+
- Mirrors Claude Code session state to Telegram: busy/idle, tool calls,
|
|
59
|
+
context %, cost, line counts
|
|
60
|
+
- Lets you reply to messages to inject prompts back into the session
|
|
61
|
+
- Surfaces permission prompts and `AskUserQuestion` dialogs as Telegram
|
|
62
|
+
inline keyboards
|
|
63
|
+
- Notifies on context warnings, compaction, session end, and stalls
|
|
64
|
+
- Supports multiple concurrent sessions with one bot
|
|
65
|
+
- Optional read-only observer bots
|
|
66
|
+
|
|
67
|
+
## Developing locally
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
git clone <repo-url> aipager && cd aipager
|
|
71
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
72
|
+
pip install -e '.[dev]'
|
|
73
|
+
pytest -q
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
When iterating on code changes you'll generally want to also install
|
|
77
|
+
`dtach-bin` from a local checkout — or `pip install dtach-bin` — so the
|
|
78
|
+
runtime can find `dtach` on PATH.
|
|
79
|
+
|
|
80
|
+
Release process is in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Observer Bots
|
|
2
|
+
|
|
3
|
+
Read-only Telegram bots that mirror notifications from the primary bot. They receive summaries, warnings, and errors — but can't control sessions.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Create a bot via [@BotFather](https://t.me/BotFather)
|
|
8
|
+
2. Start a chat with the bot and send `/start`
|
|
9
|
+
3. Get your chat ID (send a message, then check `https://api.telegram.org/bot<TOKEN>/getUpdates`)
|
|
10
|
+
4. Add to `.env`:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
OBSERVER_BOTS=<bot_token>:<chat_id>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Multiple observers (comma-separated):
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
OBSERVER_BOTS=111:AAA_first:12345,222:BBB_second:67890
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The format is `token:chat_id` — parsing uses the **last** colon as delimiter (bot tokens contain an internal colon).
|
|
23
|
+
|
|
24
|
+
5. Restart the daemon
|
|
25
|
+
|
|
26
|
+
## What observers receive
|
|
27
|
+
|
|
28
|
+
| Event | Example |
|
|
29
|
+
|-------|---------|
|
|
30
|
+
| Idle summary | "Finished" + response text (+ .txt file for long responses) |
|
|
31
|
+
| API error | "Anthropic servers overloaded" (no retry button) |
|
|
32
|
+
| Context warning | "Context at 82% — auto-compact soon" |
|
|
33
|
+
| Compacting | "Compacting" |
|
|
34
|
+
| Compact done | "Compacted: 82% → 4%" |
|
|
35
|
+
|
|
36
|
+
## What observers DON'T receive
|
|
37
|
+
|
|
38
|
+
- Busy animations / spinner
|
|
39
|
+
- Tool call updates
|
|
40
|
+
- Permission prompts (Allow/Deny)
|
|
41
|
+
- AskUserQuestion dialogs
|
|
42
|
+
- Any inline keyboards or buttons
|
|
43
|
+
|
|
44
|
+
Observers are completely stateless — fire-and-forget sends. A failing observer never affects the primary bot.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Force a TUI redraw in a dtach session by bouncing the PTY window size.
|
|
2
|
+
|
|
3
|
+
dtach has no screen buffer — reattaching shows a blank screen. TUI apps
|
|
4
|
+
(Ink/React) only redraw on genuine dimension changes due to three
|
|
5
|
+
independent same-size guards (Linux kernel, Node.js, Ink).
|
|
6
|
+
|
|
7
|
+
`redraw(name)` changes the PTY size to (rows-1, cols), waits 50ms, then
|
|
8
|
+
restores (rows, cols) — forcing two genuine SIGWINCH signals and a full
|
|
9
|
+
redraw.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import fcntl
|
|
15
|
+
import os
|
|
16
|
+
import struct
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import termios
|
|
20
|
+
import time
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_pty(session_name: str) -> str | None:
|
|
24
|
+
"""Find the PTY slave device for a claude-dtach session's child process."""
|
|
25
|
+
try:
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
["pgrep", "-f", f"dtach -n.*/claude-dtach-{session_name}\\.sock"],
|
|
28
|
+
capture_output=True, text=True, timeout=5,
|
|
29
|
+
)
|
|
30
|
+
dtach_pid = result.stdout.strip().split("\n")[0]
|
|
31
|
+
if not dtach_pid:
|
|
32
|
+
return None
|
|
33
|
+
except Exception:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
["pgrep", "-P", dtach_pid],
|
|
39
|
+
capture_output=True, text=True, timeout=5,
|
|
40
|
+
)
|
|
41
|
+
child_pid = result.stdout.strip().split("\n")[0]
|
|
42
|
+
if not child_pid:
|
|
43
|
+
return None
|
|
44
|
+
except Exception:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
pty = os.readlink(f"/proc/{child_pid}/fd/1")
|
|
49
|
+
if pty.startswith("/dev/pts/"):
|
|
50
|
+
return pty
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def bounce_size(pty_path: str) -> bool:
|
|
57
|
+
"""Bounce PTY dimensions: (rows-1) then restore. Triggers two SIGWINCHs."""
|
|
58
|
+
try:
|
|
59
|
+
fd = os.open(pty_path, os.O_RDWR | os.O_NOCTTY)
|
|
60
|
+
except OSError:
|
|
61
|
+
return False
|
|
62
|
+
try:
|
|
63
|
+
buf = fcntl.ioctl(fd, termios.TIOCGWINSZ, b"\x00" * 8)
|
|
64
|
+
rows, cols, xpix, ypix = struct.unpack("HHHH", buf)
|
|
65
|
+
if rows <= 1:
|
|
66
|
+
return False
|
|
67
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ,
|
|
68
|
+
struct.pack("HHHH", rows - 1, cols, xpix, ypix))
|
|
69
|
+
time.sleep(0.05)
|
|
70
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ,
|
|
71
|
+
struct.pack("HHHH", rows, cols, xpix, ypix))
|
|
72
|
+
return True
|
|
73
|
+
except Exception:
|
|
74
|
+
return False
|
|
75
|
+
finally:
|
|
76
|
+
os.close(fd)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def redraw(session_name: str) -> bool:
|
|
80
|
+
"""Bounce PTY size for the given session — returns True on success."""
|
|
81
|
+
pty = find_pty(session_name)
|
|
82
|
+
if not pty:
|
|
83
|
+
return False
|
|
84
|
+
return bounce_size(pty)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main() -> int:
|
|
88
|
+
if len(sys.argv) != 2:
|
|
89
|
+
print(f"Usage: {sys.argv[0]} <session-name>", file=sys.stderr)
|
|
90
|
+
return 1
|
|
91
|
+
return 0 if redraw(sys.argv[1]) else 1
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
sys.exit(main())
|