agentirc-cli 5.0.0__tar.gz → 5.0.2__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 (347) hide show
  1. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/CHANGELOG.md +26 -0
  2. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/PKG-INFO +1 -1
  3. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/mesh.py +44 -11
  4. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/daemon.py +40 -3
  5. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/irc_transport.py +39 -0
  6. {agentirc_cli-5.0.0/culture/clients/codex → agentirc_cli-5.0.2/culture/clients/acp}/message_buffer.py +8 -0
  7. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/skill/SKILL.md +22 -0
  8. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/skill/irc_client.py +15 -1
  9. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/daemon.py +40 -3
  10. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/irc_transport.py +39 -0
  11. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/message_buffer.py +8 -0
  12. {agentirc_cli-5.0.0/plugins/claude-code/skills/irc → agentirc_cli-5.0.2/culture/clients/claude/skill}/SKILL.md +22 -0
  13. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/skill/irc_client.py +15 -1
  14. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/daemon.py +63 -6
  15. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/irc_transport.py +39 -0
  16. {agentirc_cli-5.0.0/culture/clients/acp → agentirc_cli-5.0.2/culture/clients/codex}/message_buffer.py +8 -0
  17. {agentirc_cli-5.0.0/plugins/codex/skills/culture-irc → agentirc_cli-5.0.2/culture/clients/codex/skill}/SKILL.md +22 -0
  18. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/skill/irc_client.py +15 -1
  19. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/daemon.py +40 -3
  20. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/irc_transport.py +39 -0
  21. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/message_buffer.py +8 -0
  22. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/skill/SKILL.md +22 -0
  23. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/skill/irc_client.py +15 -1
  24. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/credentials.py +11 -6
  25. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/agent-harness-spec.md +2 -0
  26. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/daemon.py +50 -1
  27. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/irc_transport.py +39 -0
  28. agentirc_cli-5.0.2/packages/agent-harness/message_buffer.py +71 -0
  29. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/skill/SKILL.md +7 -0
  30. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/skill/irc_client.py +15 -1
  31. {agentirc_cli-5.0.0/culture/clients/claude/skill → agentirc_cli-5.0.2/plugins/claude-code/skills/irc}/SKILL.md +22 -0
  32. {agentirc_cli-5.0.0/culture/clients/codex/skill → agentirc_cli-5.0.2/plugins/codex/skills/culture-irc}/SKILL.md +22 -0
  33. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/pyproject.toml +1 -1
  34. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_codex_daemon.py +32 -0
  35. agentirc_cli-5.0.2/tests/test_credentials.py +24 -0
  36. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_daemon_ipc.py +57 -0
  37. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_irc_transport.py +90 -0
  38. agentirc_cli-5.0.2/tests/test_mention_warning.py +62 -0
  39. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/uv.lock +1 -1
  40. agentirc_cli-5.0.0/packages/agent-harness/message_buffer.py +0 -63
  41. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.claude/skills/pr-review/SKILL.md +0 -0
  42. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.claude/skills/run-tests/SKILL.md +0 -0
  43. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.claude/skills/run-tests/scripts/test.sh +0 -0
  44. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.flake8 +0 -0
  45. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.github/workflows/pages.yml +0 -0
  46. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.github/workflows/publish.yml +0 -0
  47. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.github/workflows/security-checks.yml +0 -0
  48. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.github/workflows/tests.yml +0 -0
  49. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.gitignore +0 -0
  50. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.markdownlint-cli2.yaml +0 -0
  51. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.pr_agent.toml +0 -0
  52. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.pre-commit-config.yaml +0 -0
  53. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/.pylintrc +0 -0
  54. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/CLAUDE.md +0 -0
  55. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/CNAME +0 -0
  56. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/Gemfile +0 -0
  57. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/Gemfile.lock +0 -0
  58. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/LICENSE +0 -0
  59. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/README.md +0 -0
  60. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/SECURITY.md +0 -0
  61. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/_config.yml +0 -0
  62. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/_includes/head_custom.html +0 -0
  63. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/_sass/color_schemes/anthropic.scss +0 -0
  64. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/_sass/custom/custom.scss +0 -0
  65. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/assets/images/apple-touch-icon.png +0 -0
  66. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/assets/images/favicon-16x16.png +0 -0
  67. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/assets/images/favicon-32x32.png +0 -0
  68. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/assets/images/favicon.ico +0 -0
  69. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/__init__.py +0 -0
  70. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/__main__.py +0 -0
  71. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/aio.py +0 -0
  72. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/bots/__init__.py +0 -0
  73. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/bots/bot.py +0 -0
  74. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/bots/bot_manager.py +0 -0
  75. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/bots/config.py +0 -0
  76. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/bots/http_listener.py +0 -0
  77. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/bots/template_engine.py +0 -0
  78. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/bots/virtual_client.py +0 -0
  79. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/__init__.py +0 -0
  80. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/agent.py +0 -0
  81. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/bot.py +0 -0
  82. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/channel.py +0 -0
  83. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/server.py +0 -0
  84. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/shared/__init__.py +0 -0
  85. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/shared/constants.py +0 -0
  86. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/shared/display.py +0 -0
  87. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/shared/formatting.py +0 -0
  88. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/shared/ipc.py +0 -0
  89. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/shared/mesh.py +0 -0
  90. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/shared/process.py +0 -0
  91. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/cli/skills.py +0 -0
  92. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/__init__.py +0 -0
  93. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/__init__.py +0 -0
  94. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/agent_runner.py +0 -0
  95. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/config.py +0 -0
  96. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/culture.yaml +0 -0
  97. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/ipc.py +0 -0
  98. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/skill/__init__.py +0 -0
  99. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/socket_server.py +0 -0
  100. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/supervisor.py +0 -0
  101. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/acp/webhook.py +0 -0
  102. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/__init__.py +0 -0
  103. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/__main__.py +0 -0
  104. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/agent_runner.py +0 -0
  105. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/config.py +0 -0
  106. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/culture.yaml +0 -0
  107. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/ipc.py +0 -0
  108. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/skill/__init__.py +0 -0
  109. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/socket_server.py +0 -0
  110. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/supervisor.py +0 -0
  111. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/claude/webhook.py +0 -0
  112. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/__init__.py +0 -0
  113. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/agent_runner.py +0 -0
  114. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/config.py +0 -0
  115. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/culture.yaml +0 -0
  116. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/ipc.py +0 -0
  117. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/skill/__init__.py +0 -0
  118. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/socket_server.py +0 -0
  119. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/supervisor.py +0 -0
  120. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/codex/webhook.py +0 -0
  121. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/__init__.py +0 -0
  122. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/agent_runner.py +0 -0
  123. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/config.py +0 -0
  124. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/culture.yaml +0 -0
  125. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/ipc.py +0 -0
  126. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/skill/__init__.py +0 -0
  127. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/socket_server.py +0 -0
  128. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/supervisor.py +0 -0
  129. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/clients/copilot/webhook.py +0 -0
  130. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/config.py +0 -0
  131. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/console/__init__.py +0 -0
  132. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/console/app.py +0 -0
  133. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/console/client.py +0 -0
  134. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/console/commands.py +0 -0
  135. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/console/widgets/__init__.py +0 -0
  136. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/console/widgets/chat.py +0 -0
  137. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/console/widgets/info_panel.py +0 -0
  138. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/console/widgets/sidebar.py +0 -0
  139. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/formatting.py +0 -0
  140. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/learn_prompt.py +0 -0
  141. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/mesh_config.py +0 -0
  142. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/observer.py +0 -0
  143. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/overview/__init__.py +0 -0
  144. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/overview/collector.py +0 -0
  145. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/overview/model.py +0 -0
  146. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/overview/renderer_text.py +0 -0
  147. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/overview/renderer_web.py +0 -0
  148. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/overview/web/style.css +0 -0
  149. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/persistence.py +0 -0
  150. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/pidfile.py +0 -0
  151. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/__init__.py +0 -0
  152. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/commands.py +0 -0
  153. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/extensions/federation.md +0 -0
  154. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/extensions/history.md +0 -0
  155. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/extensions/icons.md +0 -0
  156. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/extensions/rooms.md +0 -0
  157. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/extensions/tags.md +0 -0
  158. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/extensions/threads.md +0 -0
  159. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/message.py +0 -0
  160. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/protocol-index.md +0 -0
  161. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/protocol/replies.py +0 -0
  162. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/__init__.py +0 -0
  163. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/__main__.py +0 -0
  164. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/channel.py +0 -0
  165. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/client.py +0 -0
  166. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/config.py +0 -0
  167. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/history_store.py +0 -0
  168. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/ircd.py +0 -0
  169. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/remote_client.py +0 -0
  170. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/room_store.py +0 -0
  171. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/rooms_util.py +0 -0
  172. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/server_link.py +0 -0
  173. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/skill.py +0 -0
  174. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/skills/__init__.py +0 -0
  175. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/skills/history.py +0 -0
  176. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/skills/icon.py +0 -0
  177. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/skills/rooms.py +0 -0
  178. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/skills/threads.py +0 -0
  179. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/server/thread_store.py +0 -0
  180. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/culture/skills/culture/SKILL.md +0 -0
  181. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/agent-lifecycle.md +0 -0
  182. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/agentic-self-learn.md +0 -0
  183. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/agents/decentralized-config.md +0 -0
  184. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/agent-client.md +0 -0
  185. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/design.md +0 -0
  186. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/harness-conformance.md +0 -0
  187. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/index.md +0 -0
  188. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/layer1-core-irc.md +0 -0
  189. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/layer2-attention.md +0 -0
  190. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/layer3-skills.md +0 -0
  191. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/layer4-federation.md +0 -0
  192. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/layer5-agent-harness.md +0 -0
  193. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/server-architecture.md +0 -0
  194. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/architecture/threads.md +0 -0
  195. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/channel-polling.md +0 -0
  196. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/acp/overview.md +0 -0
  197. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/acp/system-prompt.md +0 -0
  198. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/claude/configuration.md +0 -0
  199. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/claude/context-management.md +0 -0
  200. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/claude/irc-tools.md +0 -0
  201. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/claude/overview.md +0 -0
  202. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/claude/setup.md +0 -0
  203. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/claude/supervisor.md +0 -0
  204. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/claude/webhooks.md +0 -0
  205. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/codex/configuration.md +0 -0
  206. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/codex/context-management.md +0 -0
  207. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/codex/irc-tools.md +0 -0
  208. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/codex/overview.md +0 -0
  209. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/codex/setup.md +0 -0
  210. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/codex/supervisor.md +0 -0
  211. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/codex/webhooks.md +0 -0
  212. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/copilot/configuration.md +0 -0
  213. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/copilot/context-management.md +0 -0
  214. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/copilot/irc-tools.md +0 -0
  215. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/copilot/overview.md +0 -0
  216. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/copilot/setup.md +0 -0
  217. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/copilot/supervisor.md +0 -0
  218. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/clients/copilot/webhooks.md +0 -0
  219. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/culture-cli.md +0 -0
  220. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/getting-started.md +0 -0
  221. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/index.md +0 -0
  222. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/SECURITY.md +0 -0
  223. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/bots.md +0 -0
  224. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/ci.md +0 -0
  225. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/cli.md +0 -0
  226. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/docs-site.md +0 -0
  227. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/index.md +0 -0
  228. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/ops-tooling.md +0 -0
  229. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/overview.md +0 -0
  230. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/operations/publishing.md +0 -0
  231. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/persistent-history.md +0 -0
  232. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/reflective-development.md +0 -0
  233. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  234. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/rooms.md +0 -0
  235. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/server-rename.md +0 -0
  236. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  237. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  238. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  239. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  240. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  241. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  242. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  243. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  244. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  245. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
  246. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  247. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  248. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  249. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  250. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  251. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  252. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  253. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  254. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  255. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  256. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  257. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  258. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
  259. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
  260. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
  261. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
  262. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/01-pair-programming.md +0 -0
  263. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/02-code-review-ensemble.md +0 -0
  264. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/03-cross-server-delegation.md +0 -0
  265. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/04-knowledge-propagation.md +0 -0
  266. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/05-the-observer.md +0 -0
  267. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/06-cross-server-ops.md +0 -0
  268. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/07-supervisor-intervention.md +0 -0
  269. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/08-apps-as-agents.md +0 -0
  270. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/09-research-swarm.md +0 -0
  271. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases/10-agent-lifecycle.md +0 -0
  272. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/use-cases-index.md +0 -0
  273. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/docs/what-is-culture.md +0 -0
  274. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/favicon.ico +0 -0
  275. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/README.md +0 -0
  276. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/config.py +0 -0
  277. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/culture.yaml +0 -0
  278. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/ipc.py +0 -0
  279. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/socket_server.py +0 -0
  280. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/packages/agent-harness/webhook.py +0 -0
  281. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  282. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  283. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/sonar-project.properties +0 -0
  284. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/__init__.py +0 -0
  285. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/conftest.py +0 -0
  286. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_acp_daemon.py +0 -0
  287. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_agent_runner.py +0 -0
  288. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_archive.py +0 -0
  289. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_bot.py +0 -0
  290. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_bot_config.py +0 -0
  291. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_bot_manager.py +0 -0
  292. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_bots_integration.py +0 -0
  293. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_channel.py +0 -0
  294. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_connection.py +0 -0
  295. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_console_client.py +0 -0
  296. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_console_commands.py +0 -0
  297. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_console_connection.py +0 -0
  298. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_console_icons.py +0 -0
  299. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_console_integration.py +0 -0
  300. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_copilot_daemon.py +0 -0
  301. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_culture_config.py +0 -0
  302. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_daemon.py +0 -0
  303. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_daemon_config.py +0 -0
  304. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_discovery.py +0 -0
  305. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_display.py +0 -0
  306. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_federation.py +0 -0
  307. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_history.py +0 -0
  308. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_http_listener.py +0 -0
  309. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_integration_layer5.py +0 -0
  310. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_ipc.py +0 -0
  311. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_learn_prompt.py +0 -0
  312. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_link_reconnect.py +0 -0
  313. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_mention_alias.py +0 -0
  314. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_mention_target_cleanup.py +0 -0
  315. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_mentions.py +0 -0
  316. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_mesh_config.py +0 -0
  317. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_message.py +0 -0
  318. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_message_buffer.py +0 -0
  319. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_messaging.py +0 -0
  320. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_migrate_cli.py +0 -0
  321. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_modes.py +0 -0
  322. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_overview_cli.py +0 -0
  323. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_overview_collector.py +0 -0
  324. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_overview_model.py +0 -0
  325. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_overview_renderer.py +0 -0
  326. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_overview_web.py +0 -0
  327. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_persistence.py +0 -0
  328. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_pidfile.py +0 -0
  329. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_poll_loop.py +0 -0
  330. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_register_cli.py +0 -0
  331. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_room_persistence.py +0 -0
  332. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_rooms.py +0 -0
  333. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_rooms_federation.py +0 -0
  334. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_rooms_integration.py +0 -0
  335. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_server_icon_skill.py +0 -0
  336. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_setup_update_cli.py +0 -0
  337. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_skill_client.py +0 -0
  338. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_skill_docs.py +0 -0
  339. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_skills.py +0 -0
  340. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_socket_server.py +0 -0
  341. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_supervisor.py +0 -0
  342. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_template_engine.py +0 -0
  343. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_thread_buffer.py +0 -0
  344. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_threads.py +0 -0
  345. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_virtual_client.py +0 -0
  346. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_wait_for_port.py +0 -0
  347. {agentirc_cli-5.0.0 → agentirc_cli-5.0.2}/tests/test_webhook.py +0 -0
@@ -4,6 +4,32 @@ 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.2] - 2026-04-09
8
+
9
+
10
+ ### Fixed
11
+
12
+ - Handle missing credential tool (secret-tool/security/powershell) gracefully instead of crashing the server
13
+ - Report restart failures in mesh update instead of claiming success
14
+
15
+ ## [5.0.1] - 2026-04-09
16
+
17
+
18
+ ### Added
19
+
20
+ - Topic subcommand for IRC skill (#192)
21
+ - @mention validation warnings for unknown nicks (#196)
22
+ - GitHub issues skill for Claude Code
23
+
24
+
25
+ ### Fixed
26
+
27
+ - Whitespace-only messages now rejected (#195)
28
+ - join/part channel state desync with # prefix validation (#194)
29
+ - Sending to unjoined channels now returns error (#193)
30
+ - Agents can now read own messages in channel history (#191)
31
+ - Codex backend meta-response stripping (#197)
32
+
7
33
  ## [5.0.0] - 2026-04-09
8
34
 
9
35
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 5.0.0
3
+ Version: 5.0.2
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
@@ -469,16 +469,18 @@ def _upgrade_culture_package(args: argparse.Namespace) -> bool:
469
469
  os.execvp(culture_bin, reexec_args)
470
470
 
471
471
 
472
- def _wait_for_server_port(port: int, retries: int = 50, interval: float = 0.1) -> None:
473
- """Poll until *port* accepts a TCP connection."""
472
+ def _wait_for_server_port(host: str, port: int, retries: int = 50, interval: float = 0.1) -> bool:
473
+ """Poll until *host*:*port* accepts a TCP connection. Returns True on success."""
474
474
  import socket as _socket
475
475
 
476
+ probe = "localhost" if host in ("0.0.0.0", "::", "") else host
476
477
  for _ in range(retries):
477
478
  try:
478
- with _socket.create_connection(("localhost", port), timeout=1):
479
- return
479
+ with _socket.create_connection((probe, port), timeout=1):
480
+ return True
480
481
  except OSError:
481
482
  time.sleep(interval)
483
+ return False
482
484
 
483
485
 
484
486
  def _dry_run_restart(mesh, server_name: str) -> None:
@@ -509,13 +511,16 @@ def _restart_single_service(svc_name: str, fallback_cmd: list[str], restart_serv
509
511
 
510
512
  def _restart_mesh_services(
511
513
  mesh, server_name: str, culture_bin: str, config_path: str, dry_run: bool
512
- ) -> None:
513
- """Stop agents and server, regenerate service entries, then restart everything."""
514
+ ) -> bool:
515
+ """Stop agents and server, regenerate service entries, then restart everything.
516
+
517
+ Returns True if the server came up successfully, False otherwise.
518
+ """
514
519
  print(f"Restarting mesh node '{server_name}'...")
515
520
 
516
521
  if dry_run:
517
522
  _dry_run_restart(mesh, server_name)
518
- return
523
+ return True
519
524
 
520
525
  for agent in mesh.agents:
521
526
  full_nick = f"{server_name}-{agent.nick}"
@@ -561,7 +566,17 @@ def _restart_mesh_services(
561
566
  ]
562
567
  _restart_single_service(server_svc, server_fallback, restart_service)
563
568
 
564
- _wait_for_server_port(mesh.server.port)
569
+ if not _wait_for_server_port(mesh.server.host, mesh.server.port):
570
+ if sys.platform == "linux":
571
+ hint = f"journalctl --user -u culture-server-{server_name}"
572
+ else:
573
+ hint = f"~/.culture/logs/server-{server_name}.log"
574
+ print(
575
+ f" Error: server {server_name} did not start on port {mesh.server.port}. "
576
+ f"Check logs: {hint}",
577
+ file=sys.stderr,
578
+ )
579
+ return False
565
580
 
566
581
  for agent in mesh.agents:
567
582
  full_nick = f"{server_name}-{agent.nick}"
@@ -579,6 +594,7 @@ def _restart_mesh_services(
579
594
  _restart_single_service(agent_svc, agent_fallback, restart_service)
580
595
 
581
596
  print()
597
+ return True
582
598
 
583
599
 
584
600
  def _resolve_mesh_for_server(server_name: str, config_path: str):
@@ -630,6 +646,7 @@ def _cmd_update(args: argparse.Namespace) -> None:
630
646
  culture_bin = shutil.which("culture") or "culture"
631
647
 
632
648
  running = list_servers()
649
+ failed = []
633
650
 
634
651
  if running:
635
652
  for srv in running:
@@ -640,7 +657,10 @@ def _cmd_update(args: argparse.Namespace) -> None:
640
657
  file=sys.stderr,
641
658
  )
642
659
  continue
643
- _restart_mesh_services(mesh, srv["name"], culture_bin, args.config, args.dry_run)
660
+ if not _restart_mesh_services(
661
+ mesh, srv["name"], culture_bin, args.config, args.dry_run
662
+ ):
663
+ failed.append(srv["name"])
644
664
  else:
645
665
  try:
646
666
  mesh = load_mesh_config(args.config)
@@ -648,6 +668,19 @@ def _cmd_update(args: argparse.Namespace) -> None:
648
668
  mesh = generate_mesh_from_agents(args.config)
649
669
  if mesh is None:
650
670
  sys.exit(1)
651
- _restart_mesh_services(mesh, mesh.server.name, culture_bin, args.config, args.dry_run)
671
+ if not _restart_mesh_services(
672
+ mesh, mesh.server.name, culture_bin, args.config, args.dry_run
673
+ ):
674
+ failed.append(mesh.server.name)
675
+
676
+ if failed:
677
+ print(
678
+ f"Update finished with errors. Failed to restart: {', '.join(failed)}",
679
+ file=sys.stderr,
680
+ )
681
+ sys.exit(1)
652
682
 
653
- print("Update complete. All services restarted.")
683
+ if args.dry_run:
684
+ print("Dry run complete. No services were restarted.")
685
+ else:
686
+ print("Update complete. All services restarted.")
@@ -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