ccgram 2.0.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.
- ccgram-2.0.0/.claude/rules/architecture.md +142 -0
- ccgram-2.0.0/.claude/rules/message-handling.md +40 -0
- ccgram-2.0.0/.claude/rules/topic-architecture.md +79 -0
- ccgram-2.0.0/.claude/skills/releasing/SKILL.md +38 -0
- ccgram-2.0.0/.env.example +32 -0
- ccgram-2.0.0/.github/workflows/ci.yml +33 -0
- ccgram-2.0.0/.github/workflows/release.yml +63 -0
- ccgram-2.0.0/.gitignore +55 -0
- ccgram-2.0.0/CHANGELOG.md +121 -0
- ccgram-2.0.0/CLAUDE.md +228 -0
- ccgram-2.0.0/LICENSE +22 -0
- ccgram-2.0.0/Makefile +40 -0
- ccgram-2.0.0/PKG-INFO +234 -0
- ccgram-2.0.0/README.md +193 -0
- ccgram-2.0.0/docs/ai-agents/README.md +58 -0
- ccgram-2.0.0/docs/ai-agents/architecture-map.md +101 -0
- ccgram-2.0.0/docs/ai-agents/codebase-index.md +149 -0
- ccgram-2.0.0/docs/ai-agents/extension-and-fix-playbook.md +80 -0
- ccgram-2.0.0/docs/ai-agents/tooling-and-tests.md +61 -0
- ccgram-2.0.0/docs/guides.md +347 -0
- ccgram-2.0.0/llm.txt +54 -0
- ccgram-2.0.0/pyproject.toml +141 -0
- ccgram-2.0.0/scripts/generate_homebrew_formula.py +167 -0
- ccgram-2.0.0/scripts/restart.sh +146 -0
- ccgram-2.0.0/src/ccgram/__init__.py +10 -0
- ccgram-2.0.0/src/ccgram/_version.py +34 -0
- ccgram-2.0.0/src/ccgram/bot.py +1780 -0
- ccgram-2.0.0/src/ccgram/cc_commands.py +285 -0
- ccgram-2.0.0/src/ccgram/cli.py +229 -0
- ccgram-2.0.0/src/ccgram/codex_status.py +237 -0
- ccgram-2.0.0/src/ccgram/command_catalog.py +171 -0
- ccgram-2.0.0/src/ccgram/config.py +133 -0
- ccgram-2.0.0/src/ccgram/doctor_cmd.py +334 -0
- ccgram-2.0.0/src/ccgram/fonts/JetBrainsMono-Regular.ttf +0 -0
- ccgram-2.0.0/src/ccgram/fonts/LICENSE-JetBrainsMono.txt +93 -0
- ccgram-2.0.0/src/ccgram/fonts/LICENSE-NotoSansMono.txt +92 -0
- ccgram-2.0.0/src/ccgram/fonts/LICENSE-Symbola.txt +8 -0
- ccgram-2.0.0/src/ccgram/fonts/NotoSansMonoCJKsc-Regular.otf +0 -0
- ccgram-2.0.0/src/ccgram/fonts/Symbola.ttf +0 -0
- ccgram-2.0.0/src/ccgram/handlers/__init__.py +13 -0
- ccgram-2.0.0/src/ccgram/handlers/callback_data.py +101 -0
- ccgram-2.0.0/src/ccgram/handlers/callback_helpers.py +28 -0
- ccgram-2.0.0/src/ccgram/handlers/cleanup.py +94 -0
- ccgram-2.0.0/src/ccgram/handlers/command_history.py +61 -0
- ccgram-2.0.0/src/ccgram/handlers/directory_browser.py +338 -0
- ccgram-2.0.0/src/ccgram/handlers/directory_callbacks.py +565 -0
- ccgram-2.0.0/src/ccgram/handlers/file_handler.py +272 -0
- ccgram-2.0.0/src/ccgram/handlers/history.py +201 -0
- ccgram-2.0.0/src/ccgram/handlers/history_callbacks.py +72 -0
- ccgram-2.0.0/src/ccgram/handlers/hook_events.py +269 -0
- ccgram-2.0.0/src/ccgram/handlers/interactive_callbacks.py +123 -0
- ccgram-2.0.0/src/ccgram/handlers/interactive_ui.py +327 -0
- ccgram-2.0.0/src/ccgram/handlers/message_queue.py +717 -0
- ccgram-2.0.0/src/ccgram/handlers/message_sender.py +223 -0
- ccgram-2.0.0/src/ccgram/handlers/recovery_callbacks.py +631 -0
- ccgram-2.0.0/src/ccgram/handlers/response_builder.py +95 -0
- ccgram-2.0.0/src/ccgram/handlers/restore_command.py +97 -0
- ccgram-2.0.0/src/ccgram/handlers/resume_command.py +434 -0
- ccgram-2.0.0/src/ccgram/handlers/screenshot_callbacks.py +389 -0
- ccgram-2.0.0/src/ccgram/handlers/sessions_dashboard.py +181 -0
- ccgram-2.0.0/src/ccgram/handlers/status_polling.py +1071 -0
- ccgram-2.0.0/src/ccgram/handlers/sync_command.py +262 -0
- ccgram-2.0.0/src/ccgram/handlers/text_handler.py +418 -0
- ccgram-2.0.0/src/ccgram/handlers/topic_emoji.py +220 -0
- ccgram-2.0.0/src/ccgram/handlers/upgrade.py +96 -0
- ccgram-2.0.0/src/ccgram/handlers/user_state.py +11 -0
- ccgram-2.0.0/src/ccgram/handlers/window_callbacks.py +180 -0
- ccgram-2.0.0/src/ccgram/hook.py +583 -0
- ccgram-2.0.0/src/ccgram/interactive_prompt_formatter.py +245 -0
- ccgram-2.0.0/src/ccgram/main.py +120 -0
- ccgram-2.0.0/src/ccgram/markdown_v2.py +168 -0
- ccgram-2.0.0/src/ccgram/monitor_state.py +110 -0
- ccgram-2.0.0/src/ccgram/providers/__init__.py +241 -0
- ccgram-2.0.0/src/ccgram/providers/_jsonl.py +244 -0
- ccgram-2.0.0/src/ccgram/providers/base.py +260 -0
- ccgram-2.0.0/src/ccgram/providers/claude.py +213 -0
- ccgram-2.0.0/src/ccgram/providers/codex.py +702 -0
- ccgram-2.0.0/src/ccgram/providers/gemini.py +752 -0
- ccgram-2.0.0/src/ccgram/providers/registry.py +62 -0
- ccgram-2.0.0/src/ccgram/screen_buffer.py +48 -0
- ccgram-2.0.0/src/ccgram/screenshot.py +338 -0
- ccgram-2.0.0/src/ccgram/session.py +1523 -0
- ccgram-2.0.0/src/ccgram/session_monitor.py +819 -0
- ccgram-2.0.0/src/ccgram/state_persistence.py +71 -0
- ccgram-2.0.0/src/ccgram/status_cmd.py +133 -0
- ccgram-2.0.0/src/ccgram/telegram_sender.py +43 -0
- ccgram-2.0.0/src/ccgram/terminal_parser.py +552 -0
- ccgram-2.0.0/src/ccgram/tmux_manager.py +907 -0
- ccgram-2.0.0/src/ccgram/transcript_parser.py +714 -0
- ccgram-2.0.0/src/ccgram/utils.py +223 -0
- ccgram-2.0.0/src/ccgram/window_resolver.py +200 -0
- ccgram-2.0.0/tests/ccgram/conftest.py +174 -0
- ccgram-2.0.0/tests/ccgram/handlers/__init__.py +0 -0
- ccgram-2.0.0/tests/ccgram/handlers/test_command_history.py +104 -0
- ccgram-2.0.0/tests/ccgram/handlers/test_history.py +33 -0
- ccgram-2.0.0/tests/ccgram/handlers/test_response_builder.py +58 -0
- ccgram-2.0.0/tests/ccgram/test_bot_callbacks.py +55 -0
- ccgram-2.0.0/tests/ccgram/test_callback_auth.py +31 -0
- ccgram-2.0.0/tests/ccgram/test_cc_commands.py +407 -0
- ccgram-2.0.0/tests/ccgram/test_claude_characterization.py +207 -0
- ccgram-2.0.0/tests/ccgram/test_cleanup.py +58 -0
- ccgram-2.0.0/tests/ccgram/test_cli.py +156 -0
- ccgram-2.0.0/tests/ccgram/test_codex_status.py +152 -0
- ccgram-2.0.0/tests/ccgram/test_command_catalog.py +125 -0
- ccgram-2.0.0/tests/ccgram/test_commands_command.py +292 -0
- ccgram-2.0.0/tests/ccgram/test_config.py +111 -0
- ccgram-2.0.0/tests/ccgram/test_directory_browser.py +123 -0
- ccgram-2.0.0/tests/ccgram/test_doctor_cmd.py +261 -0
- ccgram-2.0.0/tests/ccgram/test_emdash_integration.py +353 -0
- ccgram-2.0.0/tests/ccgram/test_file_handler.py +122 -0
- ccgram-2.0.0/tests/ccgram/test_forward_command.py +561 -0
- ccgram-2.0.0/tests/ccgram/test_group_filter.py +148 -0
- ccgram-2.0.0/tests/ccgram/test_handle_new_window.py +311 -0
- ccgram-2.0.0/tests/ccgram/test_hook.py +627 -0
- ccgram-2.0.0/tests/ccgram/test_hook_events.py +343 -0
- ccgram-2.0.0/tests/ccgram/test_interactive_prompt_formatter.py +153 -0
- ccgram-2.0.0/tests/ccgram/test_interactive_ui.py +114 -0
- ccgram-2.0.0/tests/ccgram/test_jsonl_providers.py +1648 -0
- ccgram-2.0.0/tests/ccgram/test_kill_command.py +101 -0
- ccgram-2.0.0/tests/ccgram/test_markdown_v2.py +126 -0
- ccgram-2.0.0/tests/ccgram/test_message_queue_properties.py +261 -0
- ccgram-2.0.0/tests/ccgram/test_message_sender.py +197 -0
- ccgram-2.0.0/tests/ccgram/test_monitor_state.py +145 -0
- ccgram-2.0.0/tests/ccgram/test_new_command.py +111 -0
- ccgram-2.0.0/tests/ccgram/test_new_window_sync.py +365 -0
- ccgram-2.0.0/tests/ccgram/test_provider_autodetect.py +331 -0
- ccgram-2.0.0/tests/ccgram/test_provider_contracts.py +424 -0
- ccgram-2.0.0/tests/ccgram/test_provider_registry.py +325 -0
- ccgram-2.0.0/tests/ccgram/test_provider_selection.py +305 -0
- ccgram-2.0.0/tests/ccgram/test_recovery_ui.py +1299 -0
- ccgram-2.0.0/tests/ccgram/test_restore_command.py +180 -0
- ccgram-2.0.0/tests/ccgram/test_resume_command.py +1009 -0
- ccgram-2.0.0/tests/ccgram/test_screen_buffer.py +133 -0
- ccgram-2.0.0/tests/ccgram/test_session.py +1035 -0
- ccgram-2.0.0/tests/ccgram/test_session_favorites.py +109 -0
- ccgram-2.0.0/tests/ccgram/test_session_monitor.py +740 -0
- ccgram-2.0.0/tests/ccgram/test_session_monitor_events.py +175 -0
- ccgram-2.0.0/tests/ccgram/test_session_notification_mode.py +132 -0
- ccgram-2.0.0/tests/ccgram/test_sessions_dashboard.py +261 -0
- ccgram-2.0.0/tests/ccgram/test_state_migration.py +32 -0
- ccgram-2.0.0/tests/ccgram/test_status_buttons.py +85 -0
- ccgram-2.0.0/tests/ccgram/test_status_cmd.py +131 -0
- ccgram-2.0.0/tests/ccgram/test_status_polling.py +1316 -0
- ccgram-2.0.0/tests/ccgram/test_status_recall_callback.py +101 -0
- ccgram-2.0.0/tests/ccgram/test_sync_command.py +390 -0
- ccgram-2.0.0/tests/ccgram/test_task_utils.py +96 -0
- ccgram-2.0.0/tests/ccgram/test_telegram_sender.py +81 -0
- ccgram-2.0.0/tests/ccgram/test_terminal_parser.py +865 -0
- ccgram-2.0.0/tests/ccgram/test_text_handler.py +483 -0
- ccgram-2.0.0/tests/ccgram/test_topic_edited.py +138 -0
- ccgram-2.0.0/tests/ccgram/test_topic_emoji.py +451 -0
- ccgram-2.0.0/tests/ccgram/test_transcript_parser.py +561 -0
- ccgram-2.0.0/tests/ccgram/test_utils.py +332 -0
- ccgram-2.0.0/tests/ccgram/test_vim_mode.py +457 -0
- ccgram-2.0.0/tests/ccgram/test_window_callbacks.py +181 -0
- ccgram-2.0.0/tests/conftest.py +14 -0
- ccgram-2.0.0/tests/e2e/__init__.py +0 -0
- ccgram-2.0.0/tests/e2e/_helpers.py +295 -0
- ccgram-2.0.0/tests/e2e/conftest.py +279 -0
- ccgram-2.0.0/tests/e2e/test_claude_lifecycle.py +326 -0
- ccgram-2.0.0/tests/e2e/test_codex_lifecycle.py +95 -0
- ccgram-2.0.0/tests/e2e/test_gemini_lifecycle.py +97 -0
- ccgram-2.0.0/tests/integration/conftest.py +86 -0
- ccgram-2.0.0/tests/integration/test_config_integration.py +39 -0
- ccgram-2.0.0/tests/integration/test_hook_pipeline.py +74 -0
- ccgram-2.0.0/tests/integration/test_message_dispatch.py +190 -0
- ccgram-2.0.0/tests/integration/test_monitor_flow.py +166 -0
- ccgram-2.0.0/tests/integration/test_monitor_state_integration.py +67 -0
- ccgram-2.0.0/tests/integration/test_state_roundtrip.py +146 -0
- ccgram-2.0.0/tests/integration/test_tmux_manager.py +222 -0
- ccgram-2.0.0/uv.lock +558 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# System Architecture
|
|
2
|
+
|
|
3
|
+
```mermaid
|
|
4
|
+
graph TB
|
|
5
|
+
subgraph bot["Telegram Bot — bot.py"]
|
|
6
|
+
direction TB
|
|
7
|
+
BotCore["Topic routing · /history · /sessions\nStatus messages · Interactive UI\nMessage queue + worker · MarkdownV2"]
|
|
8
|
+
BotSub1["markdown_v2.py\nMD → MarkdownV2 + expandable quotes"]
|
|
9
|
+
BotSub2["telegram_sender.py\nsplit_message — 4096 limit"]
|
|
10
|
+
Terminal["terminal_parser.py + screen_buffer.py\npyte VT100 · interactive UI detection\nspinner parsing · separator detection"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
subgraph monitor["SessionMonitor — session_monitor.py"]
|
|
14
|
+
Mon["Poll JSONL every 2s · mtime cache\nParse new lines · track pending tools\nRead events.jsonl incrementally"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
subgraph tmux["TmuxManager — tmux_manager.py"]
|
|
18
|
+
Tmux["list/find/create/kill windows\nsend_keys · capture_pane\nlist_panes · send_keys_to_pane"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph parsing["TranscriptParser — transcript_parser.py"]
|
|
22
|
+
TP["Parse JSONL · pair tool_use ↔ tool_result\nExpandable quotes for thinking · history"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
subgraph windows["Tmux Windows"]
|
|
26
|
+
Win["One window per topic/session\nClaude Code · Codex · Gemini"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph hook["Hook — hook.py"]
|
|
30
|
+
Hook["Receive hook stdin\nWrite session_map.json\nWrite events.jsonl"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
subgraph session["SessionManager — session.py"]
|
|
34
|
+
SM["Window ↔ Session resolution\nThread bindings · message history"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
subgraph state["State Files — ~/.ccgram/"]
|
|
38
|
+
MonState["MonitorState\nbyte offsets per session"]
|
|
39
|
+
Sessions["Claude Sessions\n~/.claude/projects/\nsessions-index + *.jsonl"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
bot -- "Notify\n(NewMessage callback)" --> monitor
|
|
43
|
+
bot -- "Send\n(tmux keys)" --> tmux
|
|
44
|
+
monitor --> parsing
|
|
45
|
+
tmux --> windows
|
|
46
|
+
windows -- "Claude Code hooks\n(7 event types)" --> hook
|
|
47
|
+
hook -- "session_map.json\n+ events.jsonl" --> session
|
|
48
|
+
session -- "reads JSONL" --> Sessions
|
|
49
|
+
monitor -- "reads" --> MonState
|
|
50
|
+
|
|
51
|
+
style bot fill:#e8f4fd,stroke:#0088cc,stroke-width:2px,color:#333
|
|
52
|
+
style monitor fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#333
|
|
53
|
+
style tmux fill:#f0faf0,stroke:#2ea44f,stroke-width:2px,color:#333
|
|
54
|
+
style parsing fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#333
|
|
55
|
+
style windows fill:#f0faf0,stroke:#2ea44f,stroke-width:2px,color:#333
|
|
56
|
+
style hook fill:#fce4ec,stroke:#c62828,stroke-width:2px,color:#333
|
|
57
|
+
style session fill:#e8eaf6,stroke:#283593,stroke-width:2px,color:#333
|
|
58
|
+
style state fill:#f5f5f5,stroke:#616161,stroke-width:2px,color:#333
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Module Inventory
|
|
62
|
+
|
|
63
|
+
### Provider modules (`providers/`)
|
|
64
|
+
|
|
65
|
+
| Module | Description |
|
|
66
|
+
| ------------- | ---------------------------------------------------------------------------------------- |
|
|
67
|
+
| `base.py` | AgentProvider protocol, ProviderCapabilities, event types |
|
|
68
|
+
| `registry.py` | ProviderRegistry (name→factory map, singleton cache) |
|
|
69
|
+
| `_jsonl.py` | Shared JSONL parsing base class for Codex + Gemini |
|
|
70
|
+
| `claude.py` | ClaudeProvider (hook, resume, continue, JSONL transcripts) |
|
|
71
|
+
| `codex.py` | CodexProvider (resume, continue, JSONL transcripts, no hook) |
|
|
72
|
+
| `gemini.py` | GeminiProvider (resume, continue, whole-file JSON transcripts, no hook) |
|
|
73
|
+
| `__init__.py` | `get_provider_for_window()`, `detect_provider_from_command()`, `get_provider()` fallback |
|
|
74
|
+
|
|
75
|
+
### Core modules (`src/ccgram/`)
|
|
76
|
+
|
|
77
|
+
| Module | Description |
|
|
78
|
+
| ------------------ | -------------------------------------------------------------------- |
|
|
79
|
+
| `cli.py` | Click-based CLI entry point (run subcommand + all bot-config flags) |
|
|
80
|
+
| `config.py` | Application configuration singleton (env vars, .env files, defaults) |
|
|
81
|
+
| `doctor_cmd.py` | `ccgram doctor [--fix]` — validate setup without bot token |
|
|
82
|
+
| `status_cmd.py` | `ccgram status` — show running state without bot token |
|
|
83
|
+
| `screen_buffer.py` | pyte VT100 screen buffer (ANSI→clean lines, separator detection) |
|
|
84
|
+
| `cc_commands.py` | CC command discovery (skills, custom commands) + menu registration |
|
|
85
|
+
| `screenshot.py` | Terminal text → PNG rendering (ANSI color, font fallback) |
|
|
86
|
+
| `main.py` | Application entry point (Click dispatcher, run_bot bootstrap) |
|
|
87
|
+
| `utils.py` | Shared utilities (ccgram_dir, tmux_session_name, atomic_write_json) |
|
|
88
|
+
|
|
89
|
+
### Handler modules (`handlers/`)
|
|
90
|
+
|
|
91
|
+
| Module | Description |
|
|
92
|
+
| -------------------------- | ------------------------------------------------------------------ |
|
|
93
|
+
| `text_handler.py` | Text message routing (UI guards → unbound → dead → forward) |
|
|
94
|
+
| `message_sender.py` | safe_reply/safe_edit/safe_send + rate_limit_send |
|
|
95
|
+
| `message_queue.py` | Per-user queue + worker (merge, status dedup) |
|
|
96
|
+
| `status_polling.py` | Background status polling (1s), auto-close, multi-pane scanning |
|
|
97
|
+
| `response_builder.py` | Response pagination and formatting |
|
|
98
|
+
| `interactive_ui.py` | AskUserQuestion / ExitPlanMode / Permission UI rendering |
|
|
99
|
+
| `interactive_callbacks.py` | Callbacks for interactive UI (arrow keys, enter, esc) |
|
|
100
|
+
| `directory_browser.py` | Directory selection UI for new topics |
|
|
101
|
+
| `directory_callbacks.py` | Callbacks for directory browser (navigate, confirm, provider pick) |
|
|
102
|
+
| `window_callbacks.py` | Window picker callbacks (bind, new, cancel) |
|
|
103
|
+
| `recovery_callbacks.py` | Dead window recovery callbacks (fresh, continue, resume) |
|
|
104
|
+
| `screenshot_callbacks.py` | Screenshot refresh, Esc, quick-key, pane screenshot callbacks |
|
|
105
|
+
| `history.py` | Message history display with pagination |
|
|
106
|
+
| `history_callbacks.py` | History pagination callbacks (prev/next) |
|
|
107
|
+
| `sessions_dashboard.py` | /sessions command: active session overview + kill |
|
|
108
|
+
| `restore_command.py` | /restore command: recover dead topics via recovery keyboard |
|
|
109
|
+
| `resume_command.py` | /resume command: scan past sessions, paginated picker |
|
|
110
|
+
| `upgrade.py` | /upgrade command: uv tool upgrade + process restart |
|
|
111
|
+
| `file_handler.py` | Photo/document handler (save to .ccgram-uploads/, notify agent) |
|
|
112
|
+
| `command_history.py` | Per-user/per-topic in-memory command recall (max 20) |
|
|
113
|
+
| `topic_emoji.py` | Topic name emoji updates (active/idle/done/dead), debounced |
|
|
114
|
+
| `hook_events.py` | Hook event dispatcher (Notification, Stop, Subagent*, Team*) |
|
|
115
|
+
| `cleanup.py` | Centralized topic state cleanup on close/delete |
|
|
116
|
+
| `callback_data.py` | CB\_\* callback data constants for inline keyboard routing |
|
|
117
|
+
| `callback_helpers.py` | Shared helpers (user_owns_window, get_thread_id) |
|
|
118
|
+
| `user_state.py` | context.user_data string key constants |
|
|
119
|
+
|
|
120
|
+
### State files (`~/.ccgram/` or `$CCBOT_DIR/`)
|
|
121
|
+
|
|
122
|
+
| File | Description |
|
|
123
|
+
| -------------------- | -------------------------------------------------------------- |
|
|
124
|
+
| `state.json` | Thread bindings + window states + display names + read offsets |
|
|
125
|
+
| `session_map.json` | Hook-generated window_id→session mapping |
|
|
126
|
+
| `events.jsonl` | Append-only hook event log (all 7 event types) |
|
|
127
|
+
| `monitor_state.json` | Poll progress (byte offset) per JSONL file |
|
|
128
|
+
|
|
129
|
+
## Key Design Decisions
|
|
130
|
+
|
|
131
|
+
- **Topic-centric** — Each Telegram topic binds to one tmux window. No centralized session list; topics _are_ the session list.
|
|
132
|
+
- **Window ID-centric** — All internal state keyed by tmux window ID (e.g. `@0`, `@12`), not window names. Window IDs are guaranteed unique within a tmux server session. Window names are kept as display names via `window_display_names` map. Same directory can have multiple windows.
|
|
133
|
+
- **Hook-based event system** — Claude Code hooks (SessionStart, Notification, Stop, SubagentStart, SubagentStop, TeammateIdle, TaskCompleted) write to `session_map.json` and `events.jsonl`. SessionMonitor reads both: session_map for session tracking, events.jsonl for instant event dispatch (interactive UI, done detection, subagent status, team notifications). Terminal scraping remains as fallback. Missing hooks are detected at startup with an actionable warning.
|
|
134
|
+
- **Multi-pane awareness** — Windows with multiple panes (e.g. Claude Code agent teams) are scanned for interactive prompts in non-active panes. Blocked panes are auto-surfaced as inline keyboard alerts. `/panes` command lists all panes with status and per-pane screenshot buttons. Callback data format extended to include pane_id: `"aq:enter:@12:%5"`.
|
|
135
|
+
- **Tool use ↔ tool result pairing** — `tool_use_id` tracked across poll cycles; tool result edits the original tool_use Telegram message in-place.
|
|
136
|
+
- **MarkdownV2 with fallback** — All messages go through `safe_reply`/`safe_edit`/`safe_send` which convert via `telegramify-markdown` and fall back to plain text on parse failure.
|
|
137
|
+
- **No truncation at parse layer** — Full content preserved; splitting at send layer respects Telegram's 4096 char limit with expandable quote atomicity.
|
|
138
|
+
- Only sessions registered in `session_map.json` (via hook) are monitored.
|
|
139
|
+
- Notifications delivered to users via thread bindings (topic → window_id → session).
|
|
140
|
+
- **Startup re-resolution** — Window IDs reset on tmux server restart. On startup, `resolve_stale_ids()` matches persisted display names against live windows to re-map IDs. Old state.json files keyed by window name are auto-migrated.
|
|
141
|
+
- **Per-window provider** — All CLI-specific behavior (launch args, transcript parsing, terminal status, command discovery) is delegated to an `AgentProvider` protocol. Providers declare capabilities (`ProviderCapabilities`) that gate UX features per-window: hook checks, resume/continue buttons, and command registration. Each window stores its `provider_name` in `WindowState`; `get_provider_for_window(window_id)` resolves the correct provider instance, falling back to the config default. Externally created windows are auto-detected via `detect_provider_from_command(pane_current_command)`. The global `get_provider()` singleton remains for CLI commands (`doctor`, `status`) that lack window context.
|
|
142
|
+
- **Foreign window support (emdash)** — Windows owned by external tools (emdash) use qualified IDs like `emdash-claude-main-abc123:@0` which are valid tmux `-t` targets. Foreign windows are marked `WindowState.external=True` and are never killed by ccgram. Discovery via `tmux list-sessions` filtered by `emdash-` prefix. The `window_resolver` preserves foreign entries during startup re-resolution. All tmux operations (send_keys, capture_pane) route foreign IDs through subprocess instead of libtmux.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Message Handling
|
|
2
|
+
|
|
3
|
+
## Message Queue Architecture
|
|
4
|
+
|
|
5
|
+
Per-user message queues + worker pattern for all send tasks:
|
|
6
|
+
- Messages are sent in receive order (FIFO)
|
|
7
|
+
- Status messages always follow content messages
|
|
8
|
+
- Multi-user concurrent processing without interference
|
|
9
|
+
|
|
10
|
+
**Message merging**: The worker automatically merges consecutive mergeable content messages on dequeue:
|
|
11
|
+
- Content messages for the same window can be merged (including text, thinking)
|
|
12
|
+
- tool_use breaks the merge chain and is sent separately (message ID recorded for later editing)
|
|
13
|
+
- tool_result breaks the merge chain and is edited into the tool_use message (preventing order confusion)
|
|
14
|
+
- Merging stops when combined length exceeds 3800 characters (to avoid pagination)
|
|
15
|
+
|
|
16
|
+
## Status Message Handling
|
|
17
|
+
|
|
18
|
+
**Conversion**: The status message is edited into the first content message, reducing message count:
|
|
19
|
+
- When a status message exists, the first content message updates it via edit
|
|
20
|
+
- Subsequent content messages are sent as new messages
|
|
21
|
+
|
|
22
|
+
**Polling**: Background task polls terminal status for all active windows at 1-second intervals. Send-layer rate limiting ensures flood control is not triggered.
|
|
23
|
+
|
|
24
|
+
**Deduplication**: The worker compares `last_text` when processing status updates; identical content skips the edit, reducing API calls.
|
|
25
|
+
|
|
26
|
+
## Rate Limiting
|
|
27
|
+
|
|
28
|
+
- Minimum 1.1-second interval between messages per user
|
|
29
|
+
- Status polling interval: 1 second (send layer has rate limiting protection)
|
|
30
|
+
- Automated outbound messages (queue worker, status updates) go through `rate_limit_send()`
|
|
31
|
+
|
|
32
|
+
## Performance Optimizations
|
|
33
|
+
|
|
34
|
+
**mtime cache**: The monitoring loop maintains an in-memory file mtime cache, skipping reads for unchanged files.
|
|
35
|
+
|
|
36
|
+
**Byte offset incremental reads**: Each tracked session records `last_byte_offset`, reading only new content. File truncation (offset > file_size) is detected and offset is auto-reset.
|
|
37
|
+
|
|
38
|
+
## No Message Truncation
|
|
39
|
+
|
|
40
|
+
Historical messages (tool_use summaries, tool_result text, user/assistant messages) are always kept in full — no character-level truncation at the parsing layer. Long text is handled exclusively at the send layer: `split_message` splits by Telegram's 4096-character limit; real-time messages get `[1/N]` text suffixes, history pages get inline keyboard navigation.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Topic-Only Architecture
|
|
2
|
+
|
|
3
|
+
The bot operates exclusively in Telegram Forum (topics) mode. There is **no** `active_sessions` mapping, **no** `/list` command, **no** General topic routing, and **no** backward-compatibility logic for older non-topic modes. Every code path assumes named topics.
|
|
4
|
+
|
|
5
|
+
## 1 Topic = 1 Window = 1 Session
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph LR
|
|
9
|
+
Topic["Topic ID\n(Telegram)"]
|
|
10
|
+
Window["Window ID\n(tmux @id)"]
|
|
11
|
+
Session["Session ID\n(Claude)"]
|
|
12
|
+
|
|
13
|
+
Topic -- "thread_bindings\n(state.json)" --> Window
|
|
14
|
+
Window -- "session_map.json\n(written by hook)" --> Session
|
|
15
|
+
|
|
16
|
+
style Topic fill:#e8f4fd,stroke:#0088cc,stroke-width:2px,color:#333
|
|
17
|
+
style Window fill:#f0faf0,stroke:#2ea44f,stroke-width:2px,color:#333
|
|
18
|
+
style Session fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#333
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Window IDs (e.g. `@0`, `@12`) are guaranteed unique within a tmux server session. Window names are stored separately as display names (`window_display_names` map).
|
|
22
|
+
|
|
23
|
+
## Mapping 1: Topic → Window ID (thread_bindings)
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
# session.py: SessionManager
|
|
27
|
+
thread_bindings: dict[int, dict[int, str]] # user_id → {thread_id → window_id}
|
|
28
|
+
window_display_names: dict[str, str] # window_id → window_name (for display)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- Storage: memory + `state.json`
|
|
32
|
+
- Written when: user creates a new session via the directory browser in a topic
|
|
33
|
+
- Purpose: route user messages to the correct tmux window
|
|
34
|
+
|
|
35
|
+
## Mapping 2: Window ID → Session (session_map.json)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# session_map.json (key format: "tmux_session:window_id")
|
|
39
|
+
{
|
|
40
|
+
"ccgram:@0": {"session_id": "uuid-xxx", "cwd": "/path/to/project", "window_name": "project", "provider_name": "claude", "transcript_path": "..."},
|
|
41
|
+
"ccgram:@5": {"session_id": "uuid-yyy", "cwd": "/path/to/project", "window_name": "project-2", "provider_name": "codex", "transcript_path": "..."}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- Storage: `session_map.json`
|
|
46
|
+
- Written when: Claude Code's `SessionStart` hook fires (always sets `provider_name: "claude"`; other providers have no hook). All 7 hook events also append to `events.jsonl` for instant dispatch.
|
|
47
|
+
- Property: one window maps to one session; session_id changes after `/clear`
|
|
48
|
+
- Purpose: SessionMonitor reads session_map to decide which sessions to watch, and reads events.jsonl for instant event notifications (interactive UI, done detection, subagent status)
|
|
49
|
+
|
|
50
|
+
## Message Flows
|
|
51
|
+
|
|
52
|
+
**Outbound** (user → Claude):
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
User sends "hello" in topic (thread_id=42)
|
|
56
|
+
→ thread_bindings[user_id][42] → "@0"
|
|
57
|
+
→ send_to_window("@0", "hello") # resolves via find_window_by_id
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Inbound** (Claude → user):
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
SessionMonitor reads new message (session_id = "uuid-xxx")
|
|
64
|
+
→ Iterate thread_bindings, find (user, thread) whose window_id maps to this session
|
|
65
|
+
→ Deliver message to user in the correct topic (thread_id)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**New topic flow**: First message in an unbound topic → directory browser → select directory → select provider → create window with chosen provider → bind topic → forward pending message.
|
|
69
|
+
|
|
70
|
+
**Topic lifecycle**: Closing/deleting a topic auto-kills the associated tmux window and unbinds the thread. Stale bindings (window deleted externally) are cleaned up by the status polling loop.
|
|
71
|
+
|
|
72
|
+
## Session Lifecycle
|
|
73
|
+
|
|
74
|
+
**Startup cleanup**: On bot startup, all tracked sessions not present in session_map are cleaned up, preventing monitoring of closed sessions.
|
|
75
|
+
|
|
76
|
+
**Runtime change detection**: Each polling cycle checks for session_map changes:
|
|
77
|
+
|
|
78
|
+
- Window's session_id changed (e.g., after `/clear`) → clean up old session
|
|
79
|
+
- Window deleted → clean up corresponding session
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: releasing
|
|
3
|
+
description: Tag version and trigger PyPI + Homebrew release. Use when user says "release", "tag and release", "publish version".
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
user-invocable: true
|
|
6
|
+
allowed-tools: Bash(git *), Bash(gh *), Bash(make check), Read
|
|
7
|
+
argument-hint: <version> (e.g., 0.3.0)
|
|
8
|
+
model: haiku
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Release v$ARGUMENTS
|
|
12
|
+
|
|
13
|
+
Tag and publish a new version to PyPI and Homebrew.
|
|
14
|
+
|
|
15
|
+
## Pre-flight
|
|
16
|
+
|
|
17
|
+
1. Verify on `main` branch: `git branch --show-current`
|
|
18
|
+
2. Verify working tree is clean: `git status`
|
|
19
|
+
3. Run `make check` — all must pass
|
|
20
|
+
4. Confirm no unpushed commits: `git log origin/main..HEAD --oneline`
|
|
21
|
+
|
|
22
|
+
## Tag and Push
|
|
23
|
+
|
|
24
|
+
5. Tag: `git tag v$ARGUMENTS`
|
|
25
|
+
6. Push tag: `git push origin v$ARGUMENTS`
|
|
26
|
+
|
|
27
|
+
## Monitor
|
|
28
|
+
|
|
29
|
+
7. Wait 30s, then check: `gh api repos/alexei-led/ccbot/actions/runs --jq '.workflow_runs[0] | "\(.name): \(.status) \(.conclusion // "running")"'`
|
|
30
|
+
8. If publish job failed, show logs and stop
|
|
31
|
+
9. If update-homebrew failed, show logs and stop
|
|
32
|
+
10. Report final status
|
|
33
|
+
|
|
34
|
+
## Notes
|
|
35
|
+
|
|
36
|
+
- hatch-vcs generates version from tag: `v0.3.0` → PyPI `0.3.0`
|
|
37
|
+
- Release workflow: `.github/workflows/release.yml`
|
|
38
|
+
- Re-tag if needed: `git tag -d v$ARGUMENTS && git push origin :refs/tags/v$ARGUMENTS && git tag v$ARGUMENTS && git push origin v$ARGUMENTS`
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Telegram Bot Token (required)
|
|
2
|
+
TELEGRAM_BOT_TOKEN=your_bot_token_here
|
|
3
|
+
|
|
4
|
+
# Allowed user IDs (comma-separated, required)
|
|
5
|
+
ALLOWED_USERS=123456789,987654321
|
|
6
|
+
|
|
7
|
+
# Tmux session name (optional, defaults to "ccgram")
|
|
8
|
+
TMUX_SESSION_NAME=ccgram
|
|
9
|
+
|
|
10
|
+
# Monitor polling interval in seconds (optional, defaults to 2.0)
|
|
11
|
+
MONITOR_POLL_INTERVAL=2.0
|
|
12
|
+
|
|
13
|
+
# Multi-instance: Telegram group chat ID this instance owns (optional)
|
|
14
|
+
# When set, this instance only processes updates from this group.
|
|
15
|
+
# Get the ID via @userinfobot or @RawDataBot in your group.
|
|
16
|
+
# CCGRAM_GROUP_ID=-1001234567890
|
|
17
|
+
|
|
18
|
+
# Multi-instance: display name for this instance (optional, defaults to hostname)
|
|
19
|
+
# CCGRAM_INSTANCE_NAME=bot-1
|
|
20
|
+
|
|
21
|
+
# Per-provider launch command overrides (optional)
|
|
22
|
+
# CCGRAM_CLAUDE_COMMAND=my-claude-wrapper (defaults to "claude")
|
|
23
|
+
# CCGRAM_CODEX_COMMAND=my-codex-wrapper (defaults to "codex")
|
|
24
|
+
# CCGRAM_GEMINI_COMMAND=my-gemini-wrapper (defaults to "gemini")
|
|
25
|
+
#
|
|
26
|
+
# Claude config directory (optional, defaults to ~/.claude)
|
|
27
|
+
# For Claude wrappers (ce, cc-mirror, zai) that use a different config location.
|
|
28
|
+
# Affects hook install, command discovery, and session monitoring.
|
|
29
|
+
# CLAUDE_CONFIG_DIR=~/.claude-custom
|
|
30
|
+
|
|
31
|
+
# Show hidden (dot) directories in the directory browser (optional, defaults to false)
|
|
32
|
+
# CCGRAM_SHOW_HIDDEN_DIRS=false
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- main
|
|
6
|
+
pull_request:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
jobs:
|
|
10
|
+
check:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version:
|
|
15
|
+
- "3.14"
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
- uses: astral-sh/setup-uv@v7
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
- run: uv sync --all-extras
|
|
24
|
+
- name: Format check
|
|
25
|
+
run: uv run ruff format --check src/ tests/
|
|
26
|
+
- name: Lint
|
|
27
|
+
run: uv run ruff check src/ tests/
|
|
28
|
+
- name: Type check
|
|
29
|
+
run: uv run pyright src/ccgram/
|
|
30
|
+
- name: Dependency check
|
|
31
|
+
run: uv run deptry src
|
|
32
|
+
- name: Test
|
|
33
|
+
run: uv run pytest --tb=short -q
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags:
|
|
5
|
+
- v*
|
|
6
|
+
jobs:
|
|
7
|
+
publish:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
environment: pypi
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
- uses: astral-sh/setup-uv@v7
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.14"
|
|
19
|
+
- run: uv build
|
|
20
|
+
- name: Publish to PyPI
|
|
21
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
22
|
+
update-homebrew:
|
|
23
|
+
needs: publish
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v6
|
|
27
|
+
- uses: astral-sh/setup-uv@v7
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.14"
|
|
30
|
+
- name: Extract version
|
|
31
|
+
id: version
|
|
32
|
+
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
|
33
|
+
- name: Generate formula
|
|
34
|
+
run: uv run scripts/generate_homebrew_formula.py "${{ steps.version.outputs.VERSION }}" > ccgram.rb
|
|
35
|
+
- name: Push to homebrew-tap
|
|
36
|
+
env:
|
|
37
|
+
GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
|
38
|
+
run: |
|
|
39
|
+
git clone "https://x-access-token:${GITHUB_TOKEN}@github.com/alexei-led/homebrew-tap.git" tap
|
|
40
|
+
cp ccgram.rb tap/Formula/ccgram.rb
|
|
41
|
+
cd tap
|
|
42
|
+
git config user.name "github-actions[bot]"
|
|
43
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
44
|
+
git add Formula/ccgram.rb
|
|
45
|
+
git commit -m "ccgram ${{ steps.version.outputs.VERSION }}"
|
|
46
|
+
git push
|
|
47
|
+
github-release:
|
|
48
|
+
needs:
|
|
49
|
+
- publish
|
|
50
|
+
- update-homebrew
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
permissions:
|
|
53
|
+
contents: write
|
|
54
|
+
steps:
|
|
55
|
+
- name: Publish GitHub Release
|
|
56
|
+
uses: softprops/action-gh-release@v2
|
|
57
|
+
with:
|
|
58
|
+
tag_name: ${{ github.ref_name }}
|
|
59
|
+
name: ${{ github.ref_name }}
|
|
60
|
+
generate_release_notes: true
|
|
61
|
+
body: |
|
|
62
|
+
Automated release for `${{ github.ref_name }}`.
|
|
63
|
+
Includes published PyPI package and Homebrew formula updates.
|
ccgram-2.0.0/.gitignore
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.so
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
|
|
8
|
+
# Build & Distribution
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
sdist/
|
|
12
|
+
wheels/
|
|
13
|
+
src/ccgram/_version.py
|
|
14
|
+
|
|
15
|
+
# Virtual Environments
|
|
16
|
+
.venv/
|
|
17
|
+
.env
|
|
18
|
+
|
|
19
|
+
# Testing & Coverage
|
|
20
|
+
.hypothesis/
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
htmlcov/
|
|
23
|
+
.coverage
|
|
24
|
+
.coverage.*
|
|
25
|
+
coverage.xml
|
|
26
|
+
coverage.json
|
|
27
|
+
|
|
28
|
+
# Type Checking & Linting
|
|
29
|
+
.mypy_cache/
|
|
30
|
+
.ruff_cache/
|
|
31
|
+
|
|
32
|
+
# Go module cache (external)
|
|
33
|
+
pkg/
|
|
34
|
+
|
|
35
|
+
# IDE
|
|
36
|
+
.idea/
|
|
37
|
+
.vscode/
|
|
38
|
+
*.swp
|
|
39
|
+
*.swo
|
|
40
|
+
*~
|
|
41
|
+
|
|
42
|
+
# Claude Code (keep .claude/ tracked)
|
|
43
|
+
.claude/settings.local.json
|
|
44
|
+
|
|
45
|
+
# Spec (keep .spec/ tracked)
|
|
46
|
+
|
|
47
|
+
# ai artificats to skip
|
|
48
|
+
.brainstorming/
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ralphex progress logs
|
|
52
|
+
progress*.txt
|
|
53
|
+
|
|
54
|
+
# Dev run lock
|
|
55
|
+
.ccgram-dev-run.lock.d/
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
## [2.0.0] - 2026-03-16
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **BREAKING**: Renamed project from `ccbot` to `ccgram` (CCGram)
|
|
15
|
+
- **BREAKING**: CLI command renamed from `ccbot` to `ccgram`
|
|
16
|
+
- **BREAKING**: Config directory changed from `~/.ccbot/` to `~/.ccgram/`
|
|
17
|
+
- **BREAKING**: Environment variables renamed from `CCBOT_*` to `CCGRAM_*`
|
|
18
|
+
- Hook command changed from `ccbot hook` to `ccgram hook`
|
|
19
|
+
- PyPI package name changed from `ccbot` to `ccgram`
|
|
20
|
+
- Default tmux session name changed from `ccbot` to `ccgram`
|
|
21
|
+
|
|
22
|
+
### Migration
|
|
23
|
+
|
|
24
|
+
- Old `CCBOT_*` environment variables still work as fallback with deprecation warnings
|
|
25
|
+
- `ccgram hook --install` detects and replaces legacy `ccbot hook` entries
|
|
26
|
+
- `ccgram hook --uninstall` removes both old and new hook entries
|
|
27
|
+
- Session map keys with `ccbot:` prefix are auto-migrated on load
|
|
28
|
+
- If `~/.ccgram/` doesn't exist but `~/.ccbot/` does, a migration hint is logged
|
|
29
|
+
|
|
30
|
+
## [1.0.0] - 2026-02-22
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Multi-provider support: Claude Code, OpenAI Codex CLI, and Google Gemini CLI as agent backends
|
|
35
|
+
- Per-topic provider selection via directory browser (Claude default, Codex, Gemini)
|
|
36
|
+
- Auto-detection of provider from externally created tmux windows
|
|
37
|
+
- Provider-aware recovery UI (Fresh/Continue/Resume adapt to each provider's capabilities)
|
|
38
|
+
- Gemini CLI terminal status detection via pane title and interactive UI patterns
|
|
39
|
+
- Codex and Gemini transcript parsing with provider-specific formats
|
|
40
|
+
- Provider capability matrix gating UX features per-window
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- Codex resume syntax corrected to `resume <id>` subcommand (was `exec resume`)
|
|
45
|
+
- Gemini resume accepts index numbers and "latest" (not just UUIDs)
|
|
46
|
+
- Both Codex and Gemini now correctly support Continue (resume last session)
|
|
47
|
+
|
|
48
|
+
## [0.2.11] - 2026-02-17
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- Preserved window display names when the SessionStart hook map is stale, preventing topic/session labels from regressing during state resolution
|
|
53
|
+
|
|
54
|
+
## [0.2.10] - 2026-02-17
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- Directory favorites sidebar plus starred MRU controls for faster session bootstrapping
|
|
59
|
+
- File handler uploads that forward captions to Claude Code alongside the document payload
|
|
60
|
+
- Notification toggle to pause/resume Telegram alerts per topic
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- Directory browser now shows status keyboard tweaks for clarity when picking working directories
|
|
65
|
+
- Status keyboard refreshed to better expose screenshot shortcuts and live indicators
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- Session polling stability improvements that cover status, screenshot, and message filtering edge cases
|
|
70
|
+
|
|
71
|
+
## [0.2.0] - 2026-02-12
|
|
72
|
+
|
|
73
|
+
Major rewrite as an independent fork of [six-ddc/ccbot](https://github.com/six-ddc/ccbot).
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- Topic-based sessions: 1 topic = 1 tmux window = 1 Claude session
|
|
78
|
+
- Interactive UI for AskUserQuestion, ExitPlanMode, and Permission prompts
|
|
79
|
+
- Sessions dashboard with per-session status and kill buttons
|
|
80
|
+
- Message history with paginated browsing (newest first)
|
|
81
|
+
- Auto-discovery of Claude Code skills and custom commands in Telegram menu
|
|
82
|
+
- Hook-based session tracking (SessionStart hook writes session_map.json)
|
|
83
|
+
- Per-user message queue with FIFO ordering and message merging
|
|
84
|
+
- Rate limiting (1.1s minimum interval per user)
|
|
85
|
+
- Multi-instance support via CCBOT_GROUP_ID and CCBOT_INSTANCE_NAME
|
|
86
|
+
- Auto-topic creation for manually created tmux windows (including cold-start)
|
|
87
|
+
- Fresh/Continue/Resume recovery flows for dead sessions
|
|
88
|
+
- /resume command to browse and resume past sessions
|
|
89
|
+
- Directory browser for new topic session creation
|
|
90
|
+
- MarkdownV2 output with automatic plain text fallback
|
|
91
|
+
- Terminal screenshot rendering (ANSI color support)
|
|
92
|
+
- Status line polling with spinner and working text
|
|
93
|
+
- Expandable quote formatting for thinking content
|
|
94
|
+
- Persistent state (thread bindings, read offsets survive restarts)
|
|
95
|
+
- Topic emoji status updates reflecting session state
|
|
96
|
+
- Configurable config directory via CCBOT_DIR env var
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- Internal routing keyed by tmux window ID instead of window name
|
|
101
|
+
- Python 3.14 required (up from 3.12)
|
|
102
|
+
- Replaced broad exception handlers with specific types
|
|
103
|
+
- Normalized variable naming (full names instead of short aliases)
|
|
104
|
+
- Enabled C901, PLR, N ruff quality gate rules
|
|
105
|
+
|
|
106
|
+
### Removed
|
|
107
|
+
|
|
108
|
+
- Non-topic mode (active_sessions, /list, General topic routing)
|
|
109
|
+
- Message truncation at parse layer (splitting only at send layer)
|
|
110
|
+
|
|
111
|
+
## [0.1.0] - 2026-02-07
|
|
112
|
+
|
|
113
|
+
Initial release by [six-ddc](https://github.com/six-ddc).
|
|
114
|
+
|
|
115
|
+
[Unreleased]: https://github.com/alexei-led/ccgram/compare/v2.0.0...HEAD
|
|
116
|
+
[2.0.0]: https://github.com/alexei-led/ccgram/compare/v1.0.0...v2.0.0
|
|
117
|
+
[1.0.0]: https://github.com/alexei-led/ccbot/compare/v0.2.11...v1.0.0
|
|
118
|
+
[0.2.11]: https://github.com/alexei-led/ccbot/compare/v0.2.10...v0.2.11
|
|
119
|
+
[0.2.10]: https://github.com/alexei-led/ccbot/compare/v0.2.0...v0.2.10
|
|
120
|
+
[0.2.0]: https://github.com/alexei-led/ccbot/compare/v0.1.0...v0.2.0
|
|
121
|
+
[0.1.0]: https://github.com/alexei-led/ccbot/releases/tag/v0.1.0
|