{"id":50838194,"url":"https://github.com/bencode/rooma","last_synced_at":"2026-06-14T05:30:35.634Z","repository":{"id":362331010,"uuid":"1245205039","full_name":"bencode/rooma","owner":"bencode","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-21T11:57:34.000Z","size":51,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-03T18:03:33.824Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bencode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-21T02:29:04.000Z","updated_at":"2026-05-21T11:57:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bencode/rooma","commit_stats":null,"previous_names":["bencode/rooma"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/bencode/rooma","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bencode%2Frooma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bencode%2Frooma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bencode%2Frooma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bencode%2Frooma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bencode","download_url":"https://codeload.github.com/bencode/rooma/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bencode%2Frooma/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34310801,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-14T05:30:34.735Z","updated_at":"2026-06-14T05:30:35.629Z","avatar_url":"https://github.com/bencode.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rooma\n\n**Chat-platform collaboration skill for AI agents.**\n\nToday: drive a Feishu (Lark) bot account from a Claude Code session — your agent can join group chats, talk to humans and peer agents, escalate when blocked, and stream every inbound message back into your conversation in real time over WebSocket. Tomorrow: same model, more transports (Slack, Discord — contributions welcome).\n\n\u003e Status: alpha. The Feishu adapter is in daily use; the public API and event schema may still shift before v1.\n\n## Why\n\nLLM agents are good at producing output but bad at being present. Most \"AI assistant\" integrations are one-shot: a webhook fires, the model responds, the conversation ends. `rooma` flips that: the agent **lives in the chat**, listening to ambient context, deciding when to speak, escalating to humans when stuck. The host process is just Claude Code (or any agent harness that can tail stdout), so you get a long-lived collaborator without standing up new infrastructure.\n\nThe core primitive is a thin CLI (`rooma send / pull / chats / members / ...`) plus a WS monitor that emits one JSON event per stdout line. Your agent reads those events as notifications and decides what to do — no servers, no databases, no message queues.\n\n## Install\n\n```bash\ngit clone https://github.com/lesscap/rooma.git\ncd rooma\npython3 -m venv .venv\n.venv/bin/pip install -e .\ncp .env.example .env\n# then edit .env with your Feishu app credentials (see \"Feishu setup\" below)\n```\n\nOptional: copy `config/contacts.yaml.example` to `config/contacts.yaml` and add chat / user aliases for nicer CLI ergonomics.\n\n## Feishu setup\n\nYou need a custom app in [open.feishu.cn](https://open.feishu.cn/) with bot capabilities enabled. The fastest path:\n\n1. **Create a custom app** in the Feishu developer console.\n2. **Enable the Bot capability** and grant these scopes: `im:message`, `im:message.group_msg`, `im:chat`, `im:resource`, `im:chat.announcement:readonly`.\n3. **Enable WebSocket events** (under \"Event Subscriptions\" → choose \"Long-connection / 长连接\"). Subscribe to `im.message.receive_v1`.\n4. **Publish a version** (apps must be published before they can connect, even in dev).\n5. **Add the bot to at least one group** you want to test in.\n6. **Read off your credentials** into `.env`:\n   - `FEISHU_APP_ID` and `FEISHU_APP_SECRET` from the app's \"Credentials\" page.\n   - `FEISHU_BOT_OPEN_ID` — the bot's own `open_id`. Easiest way: have the bot post one message in a test chat, then call `rooma pull --chat \u003cchat_id\u003e --limit 1` and look at the `sender.id` field.\n\nOnce `.env` is filled in, `rooma chats` should list the rooms the bot is in.\n\n## Usage\n\nAll commands print JSON to stdout. The shapes match Feishu's API (lightly cleaned).\n\n```bash\n# discovery\nrooma chats                                              # list every chat the bot is in\nrooma members --chat \u003cchat_id\u003e                           # who's in the room\nrooma chat-info --chat \u003cchat_id\u003e                         # name, description, announcement\n\n# read \u0026 write\nrooma pull --chat \u003cchat_id\u003e --limit 10                   # latest 10 messages, oldest first\nrooma send --chat \u003cchat_id\u003e --text \"hello\"               # plain text\nrooma send --chat \u003cchat_id\u003e --text \"hi\" --mention \u003cou_…\u003e # @-mention someone\nrooma file --chat \u003cchat_id\u003e --path /abs/path/to/file     # upload + post a file\n\n# reactions\nrooma react --message-id \u003com_…\u003e --emoji DONE             # mark as handled\n\n# escalation (posts a 🆘 message and @-mentions humans)\nrooma help --topic \"stuck on X\" --detail \"tried Y; need Z\" --chat \u003cchat_id\u003e\n```\n\nWhen you omit `--chat`, the CLI tries the `FEISHU_DEFAULT_CHAT_ID` env var, then falls back to \"the only chat the bot is in\" if there's exactly one. Aliases configured in `config/contacts.yaml` also work in place of a raw `oc_…` / `ou_…` id.\n\n## Monitor mode (the agent-in-the-chat half)\n\nThe CLI lets a script talk to Feishu. The **monitor** lets an agent **listen** to Feishu in real time:\n\n```bash\n.venv/bin/python -m rooma.monitor\n```\n\nThis holds a long-lived WebSocket to Feishu and prints one JSON event per line to stdout for every inbound message. Run it as a background process from your agent (Claude Code calls `Bash(run_in_background=true)`, then subscribes to the task's stdout via the Monitor tool); each new line arrives as a notification in the conversation. The agent reads `mentioned: true|false` to decide whether to respond.\n\nSingle-instance per app: the monitor takes a file lock keyed by `app_id`, so accidentally launching two won't split the WS stream.\n\nThe bundled Claude Code skill at `.claude/skills/rooma/SKILL.md` documents the full agent-side workflow (event schema, room-awareness rules, escalation patterns). If you're driving rooma from Claude Code, read that file — it's the operating manual.\n\n## Lifecycle messages (on-shift / off-shift)\n\nrooma can post an \"I'm here now\" message when you start a session and a goodbye when you end it — useful so groupmates (humans and peer agents) know whether the bot is actually being watched. Three pieces fit together:\n\n1. **Discovery** — `rooma sync-chats` seeds `data/known_chats.json` from the live Feishu chat list. After that, the monitor's `im.chat.member.bot.added_v1` / `_deleted_v1` handlers keep the file fresh as the bot joins / leaves groups (emitting `rooma.chat_joined` / `rooma.chat_left` events on stdout).\n2. **On-shift** — the Claude Code skill, on startup, runs `rooma chats-known` and asks you (via the AskUserQuestion picker) which chats to greet and what text to send. It then runs `rooma broadcast --chat ... --text \"...\"` and seeds the SessionEnd hook with `rooma session set --chat ... --farewell-text \"...\"`.\n3. **Off-shift** — a Claude Code **SessionEnd hook** runs `rooma session farewell`, which reads the chats + farewell text from `data/active_session.json` and broadcasts. The agent can re-tune the farewell text mid-session with `rooma session set-farewell --text \"...\"` whenever the situation changes (\"I'm signing off\" vs \"in a meeting\" vs \"back in 5\").\n\nTo enable the hook, copy `.claude/settings.local.json.example` to `.claude/settings.local.json` — the example includes a `hooks.SessionEnd` block. Both files are git-ignored.\n\nDirect CLI usage (if you want to script these yourself):\n\n```bash\nrooma sync-chats                                                # seed known chats\nrooma chats-known                                               # list locally-tracked chats\nrooma broadcast --chat oc_a --chat oc_b --text \"hello, all\"     # pure multi-send\nrooma session set --chat oc_a --farewell-text \"🌙 see you\"      # arm the farewell\nrooma session set-farewell --text \"📞 in a meeting\"             # change just the text\nrooma session show                                              # inspect current state\nrooma session farewell                                          # send + clear (hook calls this)\n```\n\nHard crashes / `kill -9` / OS shutdown don't fire the hook; the farewell silently won't go out in those cases.\n\n## Roadmap\n\n- **More transports**. The internals today are 100% Feishu (`lark_oapi` SDK calls in `actions.py` / `monitor.py`). Adding Slack / Discord / others means extracting a transport interface and writing parallel adapters. Happy to take PRs; happy to discuss the abstraction shape in an issue first.\n- **Threading / replies as first-class events**. Today every message is flat; group threads exist but aren't surfaced cleanly in the event payload.\n- **Persistent dedupe / replay** on monitor restart (today's `_handled` set is in-memory only).\n\n## Contributing\n\nIssues and PRs welcome. The codebase is small (~500 lines of Python) and deliberately function-first — no classes where a function does, no abstractions without a second consumer.\n\nConventions:\n- Files under ~200 lines, functions under ~40.\n- Comments explain **why**, not **what**.\n- Tests focus on core behavior and safety boundaries, not coverage for its own sake.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbencode%2Frooma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbencode%2Frooma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbencode%2Frooma/lists"}