This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
IMPORTANT: before you do anything else, run the beans prime command and heed its output. Tasks for this project are tracked in .beans/ via the beans CLI.
chattolib is an async Python client library for the Chatto webchat Connect API (https://chat.chatto.run/api/connect/…). Chatto migrated from GraphQL to protobuf-first ConnectRPC in v0.4.x (see ADR-042, which supersedes ADR-003). This library speaks Connect JSON over HTTP for all request/response operations.
chattolib's version tracks the Chatto server version it targets. A new Chatto release triggers a matching chattolib release (e.g. Chatto 0.4.2 → chattolib 0.4.2). If a chattolib-only fix is needed between server releases, use a post-release suffix (0.4.2.post1) rather than bumping the base number ahead of Chatto.
# Install in development mode
pip install -e ".[dev]"
# Run all tests (mocked)
pytest tests/test_client.py
# Run integration tests (require CHATTO_LOGIN / CHATTO_PASSWORD)
pytest tests/test_integration.py -v
# Lint & format
ruff check .
ruff format .
# Type checking
mypy src/chattolib
The library uses httpx for async HTTP. Realtime events are a separate
protobuf WebSocket protocol (chatto.realtime.v1); the current release ships
only a stub for that channel.
src/chattolib/ChattoClient async class. Wraps every Connect service the client currently supports; the low-level call(service, method, request) method is a public escape hatch.StrEnum types mirroring the protobuf messages, with .parse(dict) classmethods that consume Connect JSON.ChattoError, ChattoConnectError (wraps Connect protocol errors), ChattoAuthError.POST https://<host>/api/connect/<fully.qualified.Service>/<Method> with a JSON body.{}.snake_case."PRESENCE_STATUS_ONLINE". types._parse_enum also accepts the short tail ("ONLINE") for robustness.Z-suffixed). Use types.parse_datetime / types.format_datetime helpers.optional scalars: absent from JSON when unset. A present empty string is distinct.bytes: base64-encoded strings (used by MyAccountService.UploadAvatar via the ImageUpload message).{"code": "...", "message": "...", "details": [...]}. ChattoConnectError surfaces code, message, status_code, details.ChattoClient.login(login, password, base_url=…) still uses Chatto's non-Connect /auth/login HTTP endpoint. It captures both the returned bearer token and any chatto_session cookie, and every subsequent Connect call sends both.
| Domain | Service (chatto.api.v1.… unless noted) |
Notable RPCs |
|---|---|---|
| Discovery | chatto.discovery.v1.ServerDiscoveryService |
GetServer (public) |
| Server | ServerService |
GetMotd, GetRuntimeConfig |
| Viewer | ViewerService |
GetViewer |
| My account | MyAccountService |
UpdateProfile, UploadAvatar, DeleteAvatar, UpdatePassword, UpdateSettings, UpdatePresence, UpdateCustomStatus, DeleteCustomStatus, RequestAccountDeletion, DeleteMyAccount |
| Users | UserService |
ListUsers, GetUser, BatchGetUsers |
| Room directory | RoomDirectoryService |
ListRooms, ListRoomGroups, GetRoomGroup, BatchGetRoomGroups, GetRoom, BatchGetRooms |
| Rooms | RoomService |
CreateRoom, UpdateRoom, ArchiveRoom, UnarchiveRoom, JoinRoom, JoinRoomGroup, StartDM, LeaveRoom, AddMember, RemoveMember, ListMembers, GetMember, BatchGetMembers, BanMember, UnbanMember, ListBans, UpdateTypingIndicator, GetRoomEvents, GetRoomEventsAround, MarkRoomAsRead, ListRoomAttachments |
| Messages | MessageService |
FetchLinkPreview, CreateMessage, UpdateMessage, DeleteMessage, DeleteAttachment, DeleteLinkPreview, GetMessage, BatchGetMessages, AddReaction, RemoveReaction |
| Threads | ThreadService |
FollowThread, UnfollowThread, ListFollowedThreads, GetThreadEvents, GetThreadEventsAround, MarkThreadAsRead |
| Notifications | NotificationService |
ListNotifications, GetNotification, BatchGetNotifications, ListRoomNotifications, ListRoomNotificationCounts, HasNotifications, DismissNotification, DismissAllNotifications |
| Notification prefs | NotificationPreferencesService |
Get/Update × Server/Room |
| Push | PushNotificationService |
Subscribe, Unsubscribe |
| Assets | AssetService |
GetAsset, BatchGetAssets |
| Voice calls | VoiceCallService |
ListActiveCalls, GetActiveCall, BatchGetActiveCalls, JoinCall, LeaveCall, GetCallToken |
| Realtime (WS) | chatto.realtime.v1 protobuf WS |
Not yet implemented in Python — see realtime.py and the follow-up bean |
verb_noun style (create_room, post_message, mark_room_as_read); most method names mirror the Connect method with the service name dropped.snake_case; wire JSON uses camelCase and parsers translate.PascalCase (PresenceStatus, RoomKind, NotificationLevel, TimeFormat, ImageFitMode, RoomDirectoryScope, VideoProcessingStatus). Their value is the full protobuf enum-name string (e.g. "ROOM_KIND_CHANNEL").PascalCase matching the protobuf message names (Room, Message, Notification, RoomWithViewerState, etc.).RoomType enum is now RoomKind (ROOM_KIND_CHANNEL / ROOM_KIND_DM).Room no longer carries viewer-scoped state (hasUnread, etc.). The directory service returns RoomWithViewerState { room, viewerState } for that.PresenceStatus now includes UNSPECIFIED. UpdatePresence still rejects both OFFLINE and UNSPECIFIED.TimeFormat values changed: HOUR_12 / HOUR_24 / AUTO (was TWELVE_HOUR / TWENTY_FOUR_HOUR).oneof (direct_message, mention, reply, room_message); Notification.kind carries the tag.RoomTimelineEvent) are also a oneof; TimelineEvent.kind names the case (message_posted, room_created, …). Only message_posted populates a Message payload.ImageUpload message with base64-encoded bytes, not multipart. upload_avatar(path) handles the base64 encoding.server.profile; the shape is now ServerPublicProfile returned by ServerDiscoveryService.GetServer.motd on the public profile; it is a separate authenticated RPC (ServerService.GetMotd).SidebarLink items (not just rooms). RoomGroup.sidebar_links exposes them.