agentirc-cli 4.5.2__tar.gz → 5.0.1__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.
Files changed (346) hide show
  1. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/CHANGELOG.md +44 -6
  2. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/PKG-INFO +1 -1
  3. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/mesh.py +14 -2
  4. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/daemon.py +40 -3
  5. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/irc_transport.py +39 -0
  6. {agentirc_cli-4.5.2/culture/clients/codex → agentirc_cli-5.0.1/culture/clients/acp}/message_buffer.py +8 -0
  7. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/skill/SKILL.md +22 -0
  8. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/skill/irc_client.py +15 -1
  9. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/daemon.py +40 -3
  10. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/irc_transport.py +39 -0
  11. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/message_buffer.py +8 -0
  12. {agentirc_cli-4.5.2/plugins/claude-code/skills/irc → agentirc_cli-5.0.1/culture/clients/claude/skill}/SKILL.md +22 -0
  13. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/skill/irc_client.py +15 -1
  14. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/daemon.py +63 -6
  15. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/irc_transport.py +39 -0
  16. {agentirc_cli-4.5.2/culture/clients/acp → agentirc_cli-5.0.1/culture/clients/codex}/message_buffer.py +8 -0
  17. {agentirc_cli-4.5.2/plugins/codex/skills/culture-irc → agentirc_cli-5.0.1/culture/clients/codex/skill}/SKILL.md +22 -0
  18. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/skill/irc_client.py +15 -1
  19. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/daemon.py +40 -3
  20. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/irc_transport.py +39 -0
  21. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/message_buffer.py +8 -0
  22. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/skill/SKILL.md +22 -0
  23. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/skill/irc_client.py +15 -1
  24. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/collector.py +25 -0
  25. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/model.py +1 -1
  26. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/renderer_text.py +35 -18
  27. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/renderer_web.py +7 -1
  28. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/agent-harness-spec.md +2 -0
  29. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer5-agent-harness.md +3 -3
  30. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/culture-cli.md +16 -16
  31. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/getting-started.md +14 -14
  32. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/cli.md +77 -68
  33. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/overview.md +8 -8
  34. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/daemon.py +50 -1
  35. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/irc_transport.py +39 -0
  36. agentirc_cli-5.0.1/packages/agent-harness/message_buffer.py +71 -0
  37. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/skill/SKILL.md +7 -0
  38. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/skill/irc_client.py +15 -1
  39. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/plugins/claude-code/skills/culture/SKILL.md +5 -5
  40. {agentirc_cli-4.5.2/culture/clients/claude/skill → agentirc_cli-5.0.1/plugins/claude-code/skills/irc}/SKILL.md +22 -0
  41. {agentirc_cli-4.5.2/culture/clients/codex/skill → agentirc_cli-5.0.1/plugins/codex/skills/culture-irc}/SKILL.md +22 -0
  42. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/pyproject.toml +1 -1
  43. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_codex_daemon.py +32 -0
  44. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_daemon_ipc.py +57 -0
  45. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_irc_transport.py +90 -0
  46. agentirc_cli-5.0.1/tests/test_mention_warning.py +62 -0
  47. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_model.py +12 -0
  48. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_renderer.py +70 -0
  49. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/uv.lock +1 -1
  50. agentirc_cli-4.5.2/packages/agent-harness/message_buffer.py +0 -63
  51. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.claude/skills/pr-review/SKILL.md +0 -0
  52. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.claude/skills/run-tests/SKILL.md +0 -0
  53. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
  54. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.flake8 +0 -0
  55. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.github/workflows/pages.yml +0 -0
  56. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.github/workflows/publish.yml +0 -0
  57. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.github/workflows/security-checks.yml +0 -0
  58. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.github/workflows/tests.yml +0 -0
  59. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.gitignore +0 -0
  60. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.markdownlint-cli2.yaml +0 -0
  61. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.pr_agent.toml +0 -0
  62. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.pre-commit-config.yaml +0 -0
  63. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/.pylintrc +0 -0
  64. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/CLAUDE.md +0 -0
  65. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/CNAME +0 -0
  66. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/Gemfile +0 -0
  67. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/Gemfile.lock +0 -0
  68. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/LICENSE +0 -0
  69. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/README.md +0 -0
  70. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/SECURITY.md +0 -0
  71. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/_config.yml +0 -0
  72. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/_includes/head_custom.html +0 -0
  73. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/_sass/color_schemes/anthropic.scss +0 -0
  74. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/_sass/custom/custom.scss +0 -0
  75. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/assets/images/apple-touch-icon.png +0 -0
  76. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/assets/images/favicon-16x16.png +0 -0
  77. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/assets/images/favicon-32x32.png +0 -0
  78. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/assets/images/favicon.ico +0 -0
  79. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/__init__.py +0 -0
  80. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/__main__.py +0 -0
  81. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/aio.py +0 -0
  82. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/__init__.py +0 -0
  83. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/bot.py +0 -0
  84. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/bot_manager.py +0 -0
  85. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/config.py +0 -0
  86. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/http_listener.py +0 -0
  87. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/template_engine.py +0 -0
  88. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/bots/virtual_client.py +0 -0
  89. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/__init__.py +0 -0
  90. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/agent.py +0 -0
  91. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/bot.py +0 -0
  92. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/channel.py +0 -0
  93. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/server.py +0 -0
  94. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/__init__.py +0 -0
  95. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/constants.py +0 -0
  96. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/display.py +0 -0
  97. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/formatting.py +0 -0
  98. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/ipc.py +0 -0
  99. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/mesh.py +0 -0
  100. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/shared/process.py +0 -0
  101. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/cli/skills.py +0 -0
  102. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/__init__.py +0 -0
  103. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/__init__.py +0 -0
  104. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/agent_runner.py +0 -0
  105. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/config.py +0 -0
  106. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/culture.yaml +0 -0
  107. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/ipc.py +0 -0
  108. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/skill/__init__.py +0 -0
  109. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/socket_server.py +0 -0
  110. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/supervisor.py +0 -0
  111. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/acp/webhook.py +0 -0
  112. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/__init__.py +0 -0
  113. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/__main__.py +0 -0
  114. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/agent_runner.py +0 -0
  115. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/config.py +0 -0
  116. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/culture.yaml +0 -0
  117. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/ipc.py +0 -0
  118. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/skill/__init__.py +0 -0
  119. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/socket_server.py +0 -0
  120. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/supervisor.py +0 -0
  121. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/claude/webhook.py +0 -0
  122. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/__init__.py +0 -0
  123. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/agent_runner.py +0 -0
  124. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/config.py +0 -0
  125. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/culture.yaml +0 -0
  126. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/ipc.py +0 -0
  127. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/skill/__init__.py +0 -0
  128. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/socket_server.py +0 -0
  129. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/supervisor.py +0 -0
  130. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/codex/webhook.py +0 -0
  131. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/__init__.py +0 -0
  132. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/agent_runner.py +0 -0
  133. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/config.py +0 -0
  134. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/culture.yaml +0 -0
  135. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/ipc.py +0 -0
  136. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/skill/__init__.py +0 -0
  137. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/socket_server.py +0 -0
  138. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/supervisor.py +0 -0
  139. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/clients/copilot/webhook.py +0 -0
  140. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/config.py +0 -0
  141. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/__init__.py +0 -0
  142. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/app.py +0 -0
  143. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/client.py +0 -0
  144. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/commands.py +0 -0
  145. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/widgets/__init__.py +0 -0
  146. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/widgets/chat.py +0 -0
  147. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/widgets/info_panel.py +0 -0
  148. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/console/widgets/sidebar.py +0 -0
  149. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/credentials.py +0 -0
  150. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/formatting.py +0 -0
  151. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/learn_prompt.py +0 -0
  152. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/mesh_config.py +0 -0
  153. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/observer.py +0 -0
  154. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/__init__.py +0 -0
  155. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/overview/web/style.css +0 -0
  156. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/persistence.py +0 -0
  157. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/pidfile.py +0 -0
  158. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/__init__.py +0 -0
  159. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/commands.py +0 -0
  160. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/federation.md +0 -0
  161. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/history.md +0 -0
  162. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/icons.md +0 -0
  163. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/rooms.md +0 -0
  164. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/tags.md +0 -0
  165. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/extensions/threads.md +0 -0
  166. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/message.py +0 -0
  167. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/protocol-index.md +0 -0
  168. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/protocol/replies.py +0 -0
  169. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/__init__.py +0 -0
  170. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/__main__.py +0 -0
  171. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/channel.py +0 -0
  172. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/client.py +0 -0
  173. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/config.py +0 -0
  174. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/history_store.py +0 -0
  175. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/ircd.py +0 -0
  176. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/remote_client.py +0 -0
  177. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/room_store.py +0 -0
  178. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/rooms_util.py +0 -0
  179. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/server_link.py +0 -0
  180. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skill.py +0 -0
  181. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/__init__.py +0 -0
  182. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/history.py +0 -0
  183. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/icon.py +0 -0
  184. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/rooms.py +0 -0
  185. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/skills/threads.py +0 -0
  186. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/server/thread_store.py +0 -0
  187. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/culture/skills/culture/SKILL.md +0 -0
  188. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/agent-lifecycle.md +0 -0
  189. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/agentic-self-learn.md +0 -0
  190. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/agents/decentralized-config.md +0 -0
  191. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/agent-client.md +0 -0
  192. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/design.md +0 -0
  193. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/harness-conformance.md +0 -0
  194. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/index.md +0 -0
  195. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer1-core-irc.md +0 -0
  196. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer2-attention.md +0 -0
  197. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer3-skills.md +0 -0
  198. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/layer4-federation.md +0 -0
  199. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/server-architecture.md +0 -0
  200. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/architecture/threads.md +0 -0
  201. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/channel-polling.md +0 -0
  202. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/acp/overview.md +0 -0
  203. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/acp/system-prompt.md +0 -0
  204. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/configuration.md +0 -0
  205. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/context-management.md +0 -0
  206. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/irc-tools.md +0 -0
  207. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/overview.md +0 -0
  208. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/setup.md +0 -0
  209. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/supervisor.md +0 -0
  210. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/claude/webhooks.md +0 -0
  211. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/configuration.md +0 -0
  212. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/context-management.md +0 -0
  213. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/irc-tools.md +0 -0
  214. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/overview.md +0 -0
  215. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/setup.md +0 -0
  216. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/supervisor.md +0 -0
  217. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/codex/webhooks.md +0 -0
  218. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/configuration.md +0 -0
  219. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/context-management.md +0 -0
  220. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/irc-tools.md +0 -0
  221. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/overview.md +0 -0
  222. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/setup.md +0 -0
  223. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/supervisor.md +0 -0
  224. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/clients/copilot/webhooks.md +0 -0
  225. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/index.md +0 -0
  226. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/SECURITY.md +0 -0
  227. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/bots.md +0 -0
  228. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/ci.md +0 -0
  229. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/docs-site.md +0 -0
  230. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/index.md +0 -0
  231. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/ops-tooling.md +0 -0
  232. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/operations/publishing.md +0 -0
  233. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/persistent-history.md +0 -0
  234. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/reflective-development.md +0 -0
  235. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  236. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/rooms.md +0 -0
  237. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/server-rename.md +0 -0
  238. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  239. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  240. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  241. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  242. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  243. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  244. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  245. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  246. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  247. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
  248. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  249. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  250. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  251. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  252. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  253. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  254. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  255. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  256. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  257. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  258. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  259. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  260. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
  261. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
  262. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
  263. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
  264. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/01-pair-programming.md +0 -0
  265. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/02-code-review-ensemble.md +0 -0
  266. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/03-cross-server-delegation.md +0 -0
  267. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/04-knowledge-propagation.md +0 -0
  268. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/05-the-observer.md +0 -0
  269. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/06-cross-server-ops.md +0 -0
  270. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/07-supervisor-intervention.md +0 -0
  271. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/08-apps-as-agents.md +0 -0
  272. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/09-research-swarm.md +0 -0
  273. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases/10-agent-lifecycle.md +0 -0
  274. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/use-cases-index.md +0 -0
  275. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/docs/what-is-culture.md +0 -0
  276. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/favicon.ico +0 -0
  277. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/README.md +0 -0
  278. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/config.py +0 -0
  279. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/culture.yaml +0 -0
  280. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/ipc.py +0 -0
  281. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/socket_server.py +0 -0
  282. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/packages/agent-harness/webhook.py +0 -0
  283. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  284. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/sonar-project.properties +0 -0
  285. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/__init__.py +0 -0
  286. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/conftest.py +0 -0
  287. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_acp_daemon.py +0 -0
  288. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_agent_runner.py +0 -0
  289. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_archive.py +0 -0
  290. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_bot.py +0 -0
  291. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_bot_config.py +0 -0
  292. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_bot_manager.py +0 -0
  293. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_bots_integration.py +0 -0
  294. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_channel.py +0 -0
  295. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_connection.py +0 -0
  296. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_client.py +0 -0
  297. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_commands.py +0 -0
  298. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_connection.py +0 -0
  299. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_icons.py +0 -0
  300. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_console_integration.py +0 -0
  301. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_copilot_daemon.py +0 -0
  302. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_culture_config.py +0 -0
  303. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_daemon.py +0 -0
  304. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_daemon_config.py +0 -0
  305. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_discovery.py +0 -0
  306. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_display.py +0 -0
  307. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_federation.py +0 -0
  308. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_history.py +0 -0
  309. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_http_listener.py +0 -0
  310. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_integration_layer5.py +0 -0
  311. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_ipc.py +0 -0
  312. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_learn_prompt.py +0 -0
  313. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_link_reconnect.py +0 -0
  314. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_mention_alias.py +0 -0
  315. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_mention_target_cleanup.py +0 -0
  316. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_mentions.py +0 -0
  317. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_mesh_config.py +0 -0
  318. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_message.py +0 -0
  319. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_message_buffer.py +0 -0
  320. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_messaging.py +0 -0
  321. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_migrate_cli.py +0 -0
  322. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_modes.py +0 -0
  323. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_cli.py +0 -0
  324. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_collector.py +0 -0
  325. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_overview_web.py +0 -0
  326. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_persistence.py +0 -0
  327. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_pidfile.py +0 -0
  328. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_poll_loop.py +0 -0
  329. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_register_cli.py +0 -0
  330. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_room_persistence.py +0 -0
  331. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_rooms.py +0 -0
  332. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_rooms_federation.py +0 -0
  333. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_rooms_integration.py +0 -0
  334. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_server_icon_skill.py +0 -0
  335. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_setup_update_cli.py +0 -0
  336. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_skill_client.py +0 -0
  337. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_skill_docs.py +0 -0
  338. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_skills.py +0 -0
  339. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_socket_server.py +0 -0
  340. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_supervisor.py +0 -0
  341. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_template_engine.py +0 -0
  342. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_thread_buffer.py +0 -0
  343. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_threads.py +0 -0
  344. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_virtual_client.py +0 -0
  345. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_wait_for_port.py +0 -0
  346. {agentirc_cli-4.5.2 → agentirc_cli-5.0.1}/tests/test_webhook.py +0 -0
@@ -4,17 +4,55 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [5.0.1] - 2026-04-09
8
+
9
+
10
+ ### Added
11
+
12
+ - Topic subcommand for IRC skill (#192)
13
+ - @mention validation warnings for unknown nicks (#196)
14
+ - GitHub issues skill for Claude Code
15
+
16
+
17
+ ### Fixed
18
+
19
+ - Whitespace-only messages now rejected (#195)
20
+ - join/part channel state desync with # prefix validation (#194)
21
+ - Sending to unjoined channels now returns error (#193)
22
+ - Agents can now read own messages in channel history (#191)
23
+ - Codex backend meta-response stripping (#197)
24
+
25
+ ## [5.0.0] - 2026-04-09
26
+
27
+
28
+ ### Added
29
+
30
+ - Mesh overview shows stopped/registered agents from server.yaml manifest (#178)
31
+
32
+
33
+ ### Changed
34
+
35
+ - CLI docs use correct noun-group syntax (culture agent create, culture channel read, etc.) (#186)
36
+ - Replaced non-existent culture send with culture channel message / culture agent message (#187)
37
+ - All doc references updated from agents.yaml to server.yaml (#188)
38
+ - Documented --mesh-config, --webhook-port, --data-dir server start flags (#189)
39
+
40
+
41
+ ### Fixed
42
+
43
+ - Mesh overview now includes agents that are registered but not running
44
+
7
45
  ## [4.5.2] - 2026-04-09
8
46
 
9
47
 
10
48
  ### Fixed
11
49
 
12
- - Agent status shows running when circuit breaker is open (#179)
13
- - Agent status list does not distinguish paused/sleeping agents (#180)
14
- - Learn prompt missing compact/clear commands and ask --timeout (#181)
15
- - Non-Claude backend skill docs missing features - all-backends rule violation (#182)
16
- - Admin skill and learn prompt missing many CLI commands (#183)
17
- - Mesh overview shows archived bots without archive indication (#184)
50
+ - Agent status now reports the circuit-open state correctly instead of showing running (#179)
51
+ - Agent status list now distinguishes paused and sleeping agents correctly (#180)
52
+ - Learn prompt now includes compact/clear commands and ask --timeout (#181)
53
+ - Non-Claude backend skill docs now include the required features and comply with the all-backends rule (#182)
54
+ - Admin skill and learn prompt now include the missing CLI commands (#183)
55
+ - Mesh overview now indicates when bots are archived (#184)
18
56
 
19
57
  ## [4.5.1] - 2026-04-09
20
58
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 4.5.2
3
+ Version: 5.0.1
4
4
  Summary: Legacy alias for culture — install culture instead
5
5
  Project-URL: Homepage, https://github.com/OriNachum/culture
6
6
  Author: Ori Nachum
@@ -120,7 +120,13 @@ def dispatch(args: argparse.Namespace) -> None:
120
120
  # -----------------------------------------------------------------------
121
121
 
122
122
 
123
- def _collect_mesh_data(host: str, port: int, server_name: str, message_limit: int):
123
+ def _collect_mesh_data(
124
+ host: str,
125
+ port: int,
126
+ server_name: str,
127
+ message_limit: int,
128
+ manifest_agents: list | None = None,
129
+ ):
124
130
  """Collect mesh state, exiting with an error message on connection failure."""
125
131
  from culture.overview.collector import collect_mesh_state
126
132
 
@@ -131,6 +137,7 @@ def _collect_mesh_data(host: str, port: int, server_name: str, message_limit: in
131
137
  port=port,
132
138
  server_name=server_name,
133
139
  message_limit=message_limit,
140
+ manifest_agents=manifest_agents,
134
141
  )
135
142
  )
136
143
  except ConnectionRefusedError:
@@ -169,11 +176,16 @@ def _cmd_overview(args: argparse.Namespace) -> None:
169
176
  agent_filter=args.agent,
170
177
  message_limit=message_limit,
171
178
  refresh_interval=refresh_interval,
179
+ manifest_agents=config.agents,
172
180
  )
173
181
  return
174
182
 
175
183
  mesh = _collect_mesh_data(
176
- config.server.host, config.server.port, config.server.name, message_limit
184
+ config.server.host,
185
+ config.server.port,
186
+ config.server.name,
187
+ message_limit,
188
+ manifest_agents=config.agents,
177
189
  )
178
190
  output = render_text(
179
191
  mesh,
@@ -35,6 +35,9 @@ _ERR_MISSING_CHANNEL = "Missing 'channel'"
35
35
  _ERR_MISSING_CHANNEL_THREAD = "Missing 'channel' or 'thread'"
36
36
  _ERR_MISSING_CHANNEL_THREAD_MSG = "Missing 'channel', 'thread', or 'message'"
37
37
 
38
+ # Regex to extract @mentioned nicks from messages
39
+ _MENTION_RE = re.compile(r"@([\w-]+)")
40
+
38
41
  MAX_CRASH_COUNT = 3
39
42
  CRASH_WINDOW_SECONDS = 300
40
43
  CRASH_RESTART_DELAY = 5
@@ -103,6 +106,7 @@ class ACPDaemon:
103
106
  "irc_part": self._ipc_irc_part,
104
107
  "irc_channels": self._ipc_irc_channels,
105
108
  "irc_who": self._ipc_irc_who,
109
+ "irc_topic": self._ipc_irc_topic,
106
110
  "irc_ask": self._ipc_irc_ask,
107
111
  "compact": self._ipc_compact,
108
112
  "clear": self._ipc_clear,
@@ -749,16 +753,34 @@ class ACPDaemon:
749
753
  self._status_query_event = None
750
754
  self._status_query_response = ""
751
755
 
756
+ def _check_mention_warnings(self, text: str) -> list[str]:
757
+ """Return warnings for @mentioned nicks not seen in any buffer."""
758
+ mentions = _MENTION_RE.findall(text)
759
+ if not mentions or not self._buffer:
760
+ return []
761
+ known_nicks = self._buffer.known_nicks()
762
+ warnings = []
763
+ for nick in mentions:
764
+ if nick not in known_nicks:
765
+ warnings.append(f"Mentioned nick not found: {nick}")
766
+ return warnings
767
+
752
768
  async def _ipc_irc_send(self, req_id: str, msg: dict) -> dict:
753
769
  channel = msg.get("channel", "")
754
770
  text = msg.get("message", "")
755
771
  if not channel:
756
772
  return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
757
- if not text:
773
+ if not text or not text.strip():
758
774
  return make_response(req_id, ok=False, error="Missing 'message'")
759
775
  assert self._transport is not None
776
+ if channel.startswith("#") and channel not in self._transport.channels:
777
+ return make_response(req_id, ok=False, error=f"Not joined to {channel}")
760
778
  await self._transport.send_privmsg(channel, text)
761
- return make_response(req_id, ok=True)
779
+ warnings = self._check_mention_warnings(text)
780
+ resp = make_response(req_id, ok=True)
781
+ if warnings:
782
+ resp["warnings"] = warnings
783
+ return resp
762
784
 
763
785
  def _ipc_irc_read(self, req_id: str, msg: dict) -> dict:
764
786
  channel = msg.get("channel", "")
@@ -781,6 +803,8 @@ class ACPDaemon:
781
803
  channel = msg.get("channel", "")
782
804
  if not channel:
783
805
  return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
806
+ if not channel.startswith("#"):
807
+ return make_response(req_id, ok=False, error="Channel name must start with '#'")
784
808
  assert self._transport is not None
785
809
  await self._transport.join_channel(channel)
786
810
  return make_response(req_id, ok=True)
@@ -789,6 +813,8 @@ class ACPDaemon:
789
813
  channel = msg.get("channel", "")
790
814
  if not channel:
791
815
  return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
816
+ if not channel.startswith("#"):
817
+ return make_response(req_id, ok=False, error="Channel name must start with '#'")
792
818
  assert self._transport is not None
793
819
  await self._transport.part_channel(channel)
794
820
  return make_response(req_id, ok=True)
@@ -862,13 +888,24 @@ class ACPDaemon:
862
888
  await self._transport.send_who(target)
863
889
  return make_response(req_id, ok=True)
864
890
 
891
+ async def _ipc_irc_topic(self, req_id: str, msg: dict) -> dict:
892
+ channel = msg.get("channel", "")
893
+ if not channel:
894
+ return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
895
+ if not channel.startswith("#"):
896
+ return make_response(req_id, ok=False, error="Channel name must start with '#'")
897
+ assert self._transport is not None
898
+ topic = msg.get("topic") # None means query, string means set
899
+ await self._transport.send_topic(channel, topic)
900
+ return make_response(req_id, ok=True)
901
+
865
902
  async def _ipc_irc_ask(self, req_id: str, msg: dict) -> dict:
866
903
  """Send a PRIVMSG and fire a question webhook. Response matching is TODO."""
867
904
  channel = msg.get("channel", "")
868
905
  question = msg.get("message", "")
869
906
  if not channel:
870
907
  return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
871
- if not question:
908
+ if not question or not question.strip():
872
909
  return make_response(req_id, ok=False, error="Missing 'message'")
873
910
  assert self._transport is not None
874
911
  await self._transport.send_privmsg(channel, question)
@@ -51,6 +51,9 @@ class IRCTransport:
51
51
  "PRIVMSG": self._on_privmsg,
52
52
  "NOTICE": self._on_notice,
53
53
  "ROOMINVITE": self._on_roominvite,
54
+ "TOPIC": self._on_topic,
55
+ "331": self._on_numeric_topic,
56
+ "332": self._on_numeric_topic,
54
57
  }
55
58
 
56
59
  async def connect(self) -> None:
@@ -90,6 +93,10 @@ class IRCTransport:
90
93
  for line in text.splitlines():
91
94
  if line:
92
95
  await self._send_raw(f"PRIVMSG {target} :{line}")
96
+ if target.startswith("#"):
97
+ self.buffer.add(target, self.nick, line)
98
+ else:
99
+ self.buffer.add(f"DM:{target}", self.nick, line)
93
100
 
94
101
  async def send_thread_create(self, channel: str, thread_name: str, text: str) -> None:
95
102
  lines = [l for l in text.splitlines() if l]
@@ -112,11 +119,15 @@ class IRCTransport:
112
119
  await self._send_raw(f"THREADS {channel}")
113
120
 
114
121
  async def join_channel(self, channel: str) -> None:
122
+ if not channel.startswith("#"):
123
+ return
115
124
  await self._send_raw(f"JOIN {channel}")
116
125
  if channel not in self.channels:
117
126
  self.channels.append(channel)
118
127
 
119
128
  async def part_channel(self, channel: str) -> None:
129
+ if not channel.startswith("#"):
130
+ return
120
131
  await self._send_raw(f"PART {channel}")
121
132
  if channel in self.channels:
122
133
  self.channels.remove(channel)
@@ -124,6 +135,12 @@ class IRCTransport:
124
135
  async def send_who(self, target: str) -> None:
125
136
  await self._send_raw(f"WHO {target}")
126
137
 
138
+ async def send_topic(self, channel: str, topic: str | None = None) -> None:
139
+ if topic is not None:
140
+ await self._send_raw(f"TOPIC {channel} :{topic}")
141
+ else:
142
+ await self._send_raw(f"TOPIC {channel}")
143
+
127
144
  async def send_raw(self, line: str) -> None:
128
145
  """Send a raw IRC line. Public for commands like HISTORY."""
129
146
  if self._writer:
@@ -202,6 +219,28 @@ class IRCTransport:
202
219
  self._route_to_buffer(target, sender, text)
203
220
  self._detect_and_fire_mention(target, sender, text)
204
221
 
222
+ def _on_topic(self, msg: Message) -> None:
223
+ """Handle TOPIC broadcasts (someone changed the topic)."""
224
+ if len(msg.params) < 2:
225
+ return
226
+ channel = msg.params[0]
227
+ topic = msg.params[1]
228
+ sender = msg.prefix.split("!")[0] if msg.prefix else "server"
229
+ if channel.startswith("#"):
230
+ self.buffer.add(channel, sender, f"* Topic changed: {topic}")
231
+
232
+ def _on_numeric_topic(self, msg: Message) -> None:
233
+ """Handle 331 (no topic) and 332 (topic is...) replies."""
234
+ if len(msg.params) < 2:
235
+ return
236
+ channel = msg.params[1]
237
+ if not channel.startswith("#"):
238
+ return
239
+ if msg.command == "331":
240
+ self.buffer.add(channel, "server", "* No topic is set")
241
+ elif msg.command == "332" and len(msg.params) >= 3:
242
+ self.buffer.add(channel, "server", f"* Topic: {msg.params[2]}")
243
+
205
244
  def _route_to_buffer(self, target: str, sender: str, text: str) -> None:
206
245
  """Insert the message into the appropriate buffer (channel or DM)."""
207
246
  if target.startswith("#"):
@@ -53,6 +53,14 @@ class MessageBuffer:
53
53
  self._cursors[channel] = total
54
54
  return new_messages
55
55
 
56
+ def known_nicks(self) -> set[str]:
57
+ """Return the set of nicks seen across all buffers."""
58
+ nicks: set[str] = set()
59
+ for buf in self._buffers.values():
60
+ for m in buf:
61
+ nicks.add(m.nick)
62
+ return nicks
63
+
56
64
  def read_thread(self, channel: str, thread_name: str, limit: int = 50) -> list[BufferedMessage]:
57
65
  buf = self._buffers.get(channel)
58
66
  if not buf:
@@ -141,6 +141,26 @@ python3 -m culture.clients.acp.skill.irc_client who <target>
141
141
 
142
142
  ---
143
143
 
144
+ ### topic — get or set a channel topic
145
+
146
+ ```bash
147
+ python3 -m culture.clients.acp.skill.irc_client topic <channel> [topic text]
148
+ ```
149
+
150
+ Get current topic:
151
+
152
+ ```bash
153
+ python3 -m culture.clients.acp.skill.irc_client topic "#general"
154
+ ```
155
+
156
+ Set topic:
157
+
158
+ ```bash
159
+ python3 -m culture.clients.acp.skill.irc_client topic "#general" "Welcome to general chat"
160
+ ```
161
+
162
+ ---
163
+
144
164
  ### compact — compact the agent's context window
145
165
 
146
166
  ```bash
@@ -190,6 +210,8 @@ result = await client.irc_join("#ops")
190
210
  result = await client.irc_part("#ops")
191
211
  result = await client.irc_channels()
192
212
  result = await client.irc_who("#general")
213
+ result = await client.irc_topic("#general")
214
+ result = await client.irc_topic("#general", "Welcome to general chat")
193
215
  result = await client.compact()
194
216
  result = await client.clear()
195
217
 
@@ -165,6 +165,13 @@ class SkillClient:
165
165
  """Send a WHO query for a channel or nick."""
166
166
  return await self._request("irc_who", target=target)
167
167
 
168
+ async def irc_topic(self, channel: str, topic: str | None = None) -> dict[str, Any]:
169
+ """Get or set a channel topic."""
170
+ params: dict[str, Any] = {"channel": channel}
171
+ if topic is not None:
172
+ params["topic"] = topic
173
+ return await self._request("irc_topic", **params)
174
+
168
175
  async def compact(self) -> dict[str, Any]:
169
176
  """Send /compact to the agent runner."""
170
177
  return await self._request("compact")
@@ -248,6 +255,12 @@ async def _cmd_who(client: SkillClient, args: list[str]) -> dict[str, Any]:
248
255
  return await client.irc_who(args[1])
249
256
 
250
257
 
258
+ async def _cmd_topic(client: SkillClient, args: list[str]) -> dict[str, Any]:
259
+ channel = args[1]
260
+ topic = " ".join(args[2:]) if len(args) > 2 else None
261
+ return await client.irc_topic(channel, topic)
262
+
263
+
251
264
  async def _cmd_compact(client: SkillClient, args: list[str]) -> dict[str, Any]:
252
265
  return await client.compact()
253
266
 
@@ -264,6 +277,7 @@ _SUBCOMMANDS: dict[str, Any] = {
264
277
  "part": _cmd_part,
265
278
  "channels": _cmd_channels,
266
279
  "who": _cmd_who,
280
+ "topic": _cmd_topic,
267
281
  "compact": _cmd_compact,
268
282
  "clear": _cmd_clear,
269
283
  }
@@ -274,7 +288,7 @@ async def _main(args: list[str]) -> None:
274
288
  if not args:
275
289
  print(
276
290
  "Usage: irc_client.py <subcommand> [args...]\n"
277
- "Subcommands: send, read, ask, join, part, channels, who, compact, clear",
291
+ "Subcommands: send, read, ask, join, part, channels, who, topic, compact, clear",
278
292
  file=sys.stderr,
279
293
  )
280
294
  sys.exit(1)
@@ -29,6 +29,9 @@ _ERR_MISSING_CHANNEL = "Missing 'channel'"
29
29
  _ERR_MISSING_CHANNEL_THREAD = "Missing 'channel' or 'thread'"
30
30
  _ERR_MISSING_CHANNEL_THREAD_MSG = "Missing 'channel', 'thread', or 'message'"
31
31
 
32
+ # Regex to extract @mentioned nicks from messages
33
+ _MENTION_RE = re.compile(r"@([\w-]+)")
34
+
32
35
 
33
36
  class AgentDaemon:
34
37
  """Central orchestrator that ties together the IRC transport, socket server,
@@ -86,6 +89,7 @@ class AgentDaemon:
86
89
  "irc_part": self._ipc_irc_part,
87
90
  "irc_channels": self._ipc_irc_channels,
88
91
  "irc_who": self._ipc_irc_who,
92
+ "irc_topic": self._ipc_irc_topic,
89
93
  "irc_ask": self._ipc_irc_ask,
90
94
  "compact": self._ipc_compact,
91
95
  "clear": self._ipc_clear,
@@ -639,16 +643,34 @@ class AgentDaemon:
639
643
  self._status_query_event = None
640
644
  self._status_query_response = ""
641
645
 
646
+ def _check_mention_warnings(self, text: str) -> list[str]:
647
+ """Return warnings for @mentioned nicks not seen in any buffer."""
648
+ mentions = _MENTION_RE.findall(text)
649
+ if not mentions or not self._buffer:
650
+ return []
651
+ known_nicks = self._buffer.known_nicks()
652
+ warnings = []
653
+ for nick in mentions:
654
+ if nick not in known_nicks:
655
+ warnings.append(f"Mentioned nick not found: {nick}")
656
+ return warnings
657
+
642
658
  async def _ipc_irc_send(self, req_id: str, msg: dict) -> dict:
643
659
  channel = msg.get("channel", "")
644
660
  text = msg.get("message", "")
645
661
  if not channel:
646
662
  return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
647
- if not text:
663
+ if not text or not text.strip():
648
664
  return make_response(req_id, ok=False, error="Missing 'message'")
649
665
  assert self._transport is not None
666
+ if channel.startswith("#") and channel not in self._transport.channels:
667
+ return make_response(req_id, ok=False, error=f"Not joined to {channel}")
650
668
  await self._transport.send_privmsg(channel, text)
651
- return make_response(req_id, ok=True)
669
+ warnings = self._check_mention_warnings(text)
670
+ resp = make_response(req_id, ok=True)
671
+ if warnings:
672
+ resp["warnings"] = warnings
673
+ return resp
652
674
 
653
675
  def _ipc_irc_read(self, req_id: str, msg: dict) -> dict:
654
676
  channel = msg.get("channel", "")
@@ -671,6 +693,8 @@ class AgentDaemon:
671
693
  channel = msg.get("channel", "")
672
694
  if not channel:
673
695
  return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
696
+ if not channel.startswith("#"):
697
+ return make_response(req_id, ok=False, error="Channel name must start with '#'")
674
698
  assert self._transport is not None
675
699
  await self._transport.join_channel(channel)
676
700
  return make_response(req_id, ok=True)
@@ -679,6 +703,8 @@ class AgentDaemon:
679
703
  channel = msg.get("channel", "")
680
704
  if not channel:
681
705
  return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
706
+ if not channel.startswith("#"):
707
+ return make_response(req_id, ok=False, error="Channel name must start with '#'")
682
708
  assert self._transport is not None
683
709
  await self._transport.part_channel(channel)
684
710
  return make_response(req_id, ok=True)
@@ -752,13 +778,24 @@ class AgentDaemon:
752
778
  await self._transport.send_who(target)
753
779
  return make_response(req_id, ok=True)
754
780
 
781
+ async def _ipc_irc_topic(self, req_id: str, msg: dict) -> dict:
782
+ channel = msg.get("channel", "")
783
+ if not channel:
784
+ return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
785
+ if not channel.startswith("#"):
786
+ return make_response(req_id, ok=False, error="Channel name must start with '#'")
787
+ assert self._transport is not None
788
+ topic = msg.get("topic") # None means query, string means set
789
+ await self._transport.send_topic(channel, topic)
790
+ return make_response(req_id, ok=True)
791
+
755
792
  async def _ipc_irc_ask(self, req_id: str, msg: dict) -> dict:
756
793
  """Send a PRIVMSG and fire a question webhook. Response matching is TODO."""
757
794
  channel = msg.get("channel", "")
758
795
  question = msg.get("message", "")
759
796
  if not channel:
760
797
  return make_response(req_id, ok=False, error=_ERR_MISSING_CHANNEL)
761
- if not question:
798
+ if not question or not question.strip():
762
799
  return make_response(req_id, ok=False, error="Missing 'message'")
763
800
  assert self._transport is not None
764
801
  await self._transport.send_privmsg(channel, question)
@@ -51,6 +51,9 @@ class IRCTransport:
51
51
  "PRIVMSG": self._on_privmsg,
52
52
  "NOTICE": self._on_notice,
53
53
  "ROOMINVITE": self._on_roominvite,
54
+ "TOPIC": self._on_topic,
55
+ "331": self._on_numeric_topic,
56
+ "332": self._on_numeric_topic,
54
57
  }
55
58
 
56
59
  async def connect(self) -> None:
@@ -90,6 +93,10 @@ class IRCTransport:
90
93
  for line in text.splitlines():
91
94
  if line:
92
95
  await self._send_raw(f"PRIVMSG {target} :{line}")
96
+ if target.startswith("#"):
97
+ self.buffer.add(target, self.nick, line)
98
+ else:
99
+ self.buffer.add(f"DM:{target}", self.nick, line)
93
100
 
94
101
  async def send_thread_create(self, channel: str, thread_name: str, text: str) -> None:
95
102
  lines = [l for l in text.splitlines() if l]
@@ -112,11 +119,15 @@ class IRCTransport:
112
119
  await self._send_raw(f"THREADS {channel}")
113
120
 
114
121
  async def join_channel(self, channel: str) -> None:
122
+ if not channel.startswith("#"):
123
+ return
115
124
  await self._send_raw(f"JOIN {channel}")
116
125
  if channel not in self.channels:
117
126
  self.channels.append(channel)
118
127
 
119
128
  async def part_channel(self, channel: str) -> None:
129
+ if not channel.startswith("#"):
130
+ return
120
131
  await self._send_raw(f"PART {channel}")
121
132
  if channel in self.channels:
122
133
  self.channels.remove(channel)
@@ -124,6 +135,12 @@ class IRCTransport:
124
135
  async def send_who(self, target: str) -> None:
125
136
  await self._send_raw(f"WHO {target}")
126
137
 
138
+ async def send_topic(self, channel: str, topic: str | None = None) -> None:
139
+ if topic is not None:
140
+ await self._send_raw(f"TOPIC {channel} :{topic}")
141
+ else:
142
+ await self._send_raw(f"TOPIC {channel}")
143
+
127
144
  async def send_raw(self, line: str) -> None:
128
145
  """Send a raw IRC line. Public for commands like HISTORY."""
129
146
  if self._writer:
@@ -202,6 +219,28 @@ class IRCTransport:
202
219
  self._route_to_buffer(target, sender, text)
203
220
  self._detect_and_fire_mention(target, sender, text)
204
221
 
222
+ def _on_topic(self, msg: Message) -> None:
223
+ """Handle TOPIC broadcasts (someone changed the topic)."""
224
+ if len(msg.params) < 2:
225
+ return
226
+ channel = msg.params[0]
227
+ topic = msg.params[1]
228
+ sender = msg.prefix.split("!")[0] if msg.prefix else "server"
229
+ if channel.startswith("#"):
230
+ self.buffer.add(channel, sender, f"* Topic changed: {topic}")
231
+
232
+ def _on_numeric_topic(self, msg: Message) -> None:
233
+ """Handle 331 (no topic) and 332 (topic is...) replies."""
234
+ if len(msg.params) < 2:
235
+ return
236
+ channel = msg.params[1]
237
+ if not channel.startswith("#"):
238
+ return
239
+ if msg.command == "331":
240
+ self.buffer.add(channel, "server", "* No topic is set")
241
+ elif msg.command == "332" and len(msg.params) >= 3:
242
+ self.buffer.add(channel, "server", f"* Topic: {msg.params[2]}")
243
+
205
244
  def _route_to_buffer(self, target: str, sender: str, text: str) -> None:
206
245
  """Insert the message into the appropriate buffer (channel or DM)."""
207
246
  if target.startswith("#"):
@@ -53,6 +53,14 @@ class MessageBuffer:
53
53
  self._cursors[channel] = total
54
54
  return new_messages
55
55
 
56
+ def known_nicks(self) -> set[str]:
57
+ """Return the set of nicks seen across all buffers."""
58
+ nicks: set[str] = set()
59
+ for buf in self._buffers.values():
60
+ for m in buf:
61
+ nicks.add(m.nick)
62
+ return nicks
63
+
56
64
  def read_thread(self, channel: str, thread_name: str, limit: int = 50) -> list[BufferedMessage]:
57
65
  buf = self._buffers.get(channel)
58
66
  if not buf:
@@ -134,6 +134,26 @@ python3 -m culture.clients.claude.skill.irc_client who <target>
134
134
 
135
135
  ---
136
136
 
137
+ ### topic — get or set a channel topic
138
+
139
+ ```bash
140
+ python3 -m culture.clients.claude.skill.irc_client topic <channel> [topic text]
141
+ ```
142
+
143
+ Get current topic:
144
+
145
+ ```bash
146
+ python3 -m culture.clients.claude.skill.irc_client topic "#general"
147
+ ```
148
+
149
+ Set topic:
150
+
151
+ ```bash
152
+ python3 -m culture.clients.claude.skill.irc_client topic "#general" "Welcome to general chat"
153
+ ```
154
+
155
+ ---
156
+
137
157
  ### compact — compact the agent's context window
138
158
 
139
159
  ```bash
@@ -183,6 +203,8 @@ result = await client.irc_join("#ops")
183
203
  result = await client.irc_part("#ops")
184
204
  result = await client.irc_channels()
185
205
  result = await client.irc_who("#general")
206
+ result = await client.irc_topic("#general")
207
+ result = await client.irc_topic("#general", "Welcome to general chat")
186
208
  result = await client.compact()
187
209
  result = await client.clear()
188
210
 
@@ -162,6 +162,13 @@ class SkillClient:
162
162
  """Send a WHO query for a channel or nick."""
163
163
  return await self._request("irc_who", target=target)
164
164
 
165
+ async def irc_topic(self, channel: str, topic: str | None = None) -> dict[str, Any]:
166
+ """Get or set a channel topic."""
167
+ params: dict[str, Any] = {"channel": channel}
168
+ if topic is not None:
169
+ params["topic"] = topic
170
+ return await self._request("irc_topic", **params)
171
+
165
172
  async def compact(self) -> dict[str, Any]:
166
173
  """Send /compact to the Claude agent runner."""
167
174
  return await self._request("compact")
@@ -245,6 +252,12 @@ async def _cmd_who(client: SkillClient, args: list[str]) -> dict[str, Any]:
245
252
  return await client.irc_who(args[1])
246
253
 
247
254
 
255
+ async def _cmd_topic(client: SkillClient, args: list[str]) -> dict[str, Any]:
256
+ channel = args[1]
257
+ topic = " ".join(args[2:]) if len(args) > 2 else None
258
+ return await client.irc_topic(channel, topic)
259
+
260
+
248
261
  async def _cmd_compact(client: SkillClient, args: list[str]) -> dict[str, Any]:
249
262
  return await client.compact()
250
263
 
@@ -261,6 +274,7 @@ _SUBCOMMANDS: dict[str, Any] = {
261
274
  "part": _cmd_part,
262
275
  "channels": _cmd_channels,
263
276
  "who": _cmd_who,
277
+ "topic": _cmd_topic,
264
278
  "compact": _cmd_compact,
265
279
  "clear": _cmd_clear,
266
280
  }
@@ -271,7 +285,7 @@ async def _main(args: list[str]) -> None:
271
285
  if not args:
272
286
  print(
273
287
  "Usage: irc_client.py <subcommand> [args...]\n"
274
- "Subcommands: send, read, ask, join, part, channels, who, compact, clear",
288
+ "Subcommands: send, read, ask, join, part, channels, who, topic, compact, clear",
275
289
  file=sys.stderr,
276
290
  )
277
291
  sys.exit(1)