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.
@@ -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
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ .env
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .venv/
9
+ venv/
10
+ *.sock
11
+ .claude/
12
+ tasks/
13
+ PLAN.md
14
+ .pytest_cache/
15
+ .ruff_cache/
@@ -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).
@@ -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,8 @@
1
+ """aipager — Telegram remote control for Claude Code sessions."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("aipager")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0+unknown"
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m aipager` — delegates to the CLI."""
2
+
3
+ from aipager.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -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())