agentirc-cli 7.1.5__tar.gz → 7.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/CHANGELOG.md +11 -0
  2. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/PKG-INFO +1 -1
  3. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/app.py +21 -34
  4. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/widgets/chat.py +63 -6
  5. agentirc_cli-7.2.0/docs/reference/console.md +90 -0
  6. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/pyproject.toml +1 -1
  7. agentirc_cli-7.2.0/tests/test_console_chat_markdown.py +241 -0
  8. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/uv.lock +1 -1
  9. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.claude/agents/doc-test-alignment.md +0 -0
  10. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.claude/skills/pr-review/SKILL.md +0 -0
  11. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.claude/skills/run-tests/SKILL.md +0 -0
  12. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  13. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.flake8 +0 -0
  14. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.github/workflows/docs-check.yml +0 -0
  15. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.github/workflows/publish.yml +0 -0
  16. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.github/workflows/security-checks.yml +0 -0
  17. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.github/workflows/tests.yml +0 -0
  18. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.gitignore +0 -0
  19. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.markdownlint-cli2.yaml +0 -0
  20. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.pr_agent.toml +0 -0
  21. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.pre-commit-config.yaml +0 -0
  22. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/.pylintrc +0 -0
  23. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/CLAUDE.md +0 -0
  24. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/Gemfile +0 -0
  25. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/Gemfile.lock +0 -0
  26. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/LICENSE +0 -0
  27. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/README.md +0 -0
  28. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/SECURITY.md +0 -0
  29. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_config.agentirc.yml +0 -0
  30. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_config.base.yml +0 -0
  31. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_config.culture.yml +0 -0
  32. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_data/sites.yml +0 -0
  33. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_includes/head_custom.html +0 -0
  34. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_plugins/site_filter.rb +0 -0
  35. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_sass/color_schemes/anthropic.scss +0 -0
  36. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_sass/color_schemes/dark-terminal.scss +0 -0
  37. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/_sass/custom/custom.scss +0 -0
  38. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/assets/images/IMG_3183.png +0 -0
  39. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/assets/images/apple-touch-icon.png +0 -0
  40. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/assets/images/favicon-16x16.png +0 -0
  41. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/assets/images/favicon-32x32.png +0 -0
  42. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/assets/images/favicon.ico +0 -0
  43. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/assets/images/og-agentirc.png +0 -0
  44. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/assets/images/og-culture.png +0 -0
  45. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/__init__.py +0 -0
  46. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/__main__.py +0 -0
  47. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/CLAUDE.md +0 -0
  48. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/__init__.py +0 -0
  49. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/__main__.py +0 -0
  50. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/channel.py +0 -0
  51. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/client.py +0 -0
  52. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/config.py +0 -0
  53. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/docs/agentirc-architecture.md +0 -0
  54. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/docs/agentirc-features.md +0 -0
  55. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/docs/agentirc-skill.md +0 -0
  56. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/docs/agentirc.md +0 -0
  57. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/events.py +0 -0
  58. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/history_store.py +0 -0
  59. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/ircd.py +0 -0
  60. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/remote_client.py +0 -0
  61. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/room_store.py +0 -0
  62. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/rooms_util.py +0 -0
  63. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/server_link.py +0 -0
  64. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/skill.py +0 -0
  65. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/skills/__init__.py +0 -0
  66. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/skills/history.py +0 -0
  67. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/skills/icon.py +0 -0
  68. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/skills/rooms.py +0 -0
  69. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/skills/threads.py +0 -0
  70. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/agentirc/thread_store.py +0 -0
  71. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/aio.py +0 -0
  72. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/__init__.py +0 -0
  73. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/bot.py +0 -0
  74. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/bot_manager.py +0 -0
  75. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/config.py +0 -0
  76. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/filter_dsl.py +0 -0
  77. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/http_listener.py +0 -0
  78. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/system/__init__.py +0 -0
  79. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/system/welcome/__init__.py +0 -0
  80. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/system/welcome/bot.yaml +0 -0
  81. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/system/welcome/handler.py +0 -0
  82. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/template_engine.py +0 -0
  83. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/bots/virtual_client.py +0 -0
  84. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/__init__.py +0 -0
  85. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/agent.py +0 -0
  86. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/bot.py +0 -0
  87. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/channel.py +0 -0
  88. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/mesh.py +0 -0
  89. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/server.py +0 -0
  90. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/shared/__init__.py +0 -0
  91. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/shared/constants.py +0 -0
  92. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/shared/display.py +0 -0
  93. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/shared/formatting.py +0 -0
  94. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/shared/ipc.py +0 -0
  95. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/shared/mesh.py +0 -0
  96. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/shared/process.py +0 -0
  97. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/cli/skills.py +0 -0
  98. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/__init__.py +0 -0
  99. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/__init__.py +0 -0
  100. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/agent_runner.py +0 -0
  101. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/config.py +0 -0
  102. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/culture.yaml +0 -0
  103. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/daemon.py +0 -0
  104. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/ipc.py +0 -0
  105. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/irc_transport.py +0 -0
  106. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/message_buffer.py +0 -0
  107. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/skill/SKILL.md +0 -0
  108. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/skill/__init__.py +0 -0
  109. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/skill/irc_client.py +0 -0
  110. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/socket_server.py +0 -0
  111. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/supervisor.py +0 -0
  112. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/acp/webhook.py +0 -0
  113. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/__init__.py +0 -0
  114. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/__main__.py +0 -0
  115. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/agent_runner.py +0 -0
  116. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/config.py +0 -0
  117. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/culture.yaml +0 -0
  118. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/daemon.py +0 -0
  119. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/ipc.py +0 -0
  120. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/irc_transport.py +0 -0
  121. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/message_buffer.py +0 -0
  122. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/skill/SKILL.md +0 -0
  123. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/skill/__init__.py +0 -0
  124. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/skill/irc_client.py +0 -0
  125. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/socket_server.py +0 -0
  126. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/supervisor.py +0 -0
  127. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/claude/webhook.py +0 -0
  128. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/__init__.py +0 -0
  129. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/agent_runner.py +0 -0
  130. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/config.py +0 -0
  131. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/culture.yaml +0 -0
  132. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/daemon.py +0 -0
  133. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/ipc.py +0 -0
  134. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/irc_transport.py +0 -0
  135. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/message_buffer.py +0 -0
  136. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/skill/SKILL.md +0 -0
  137. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/skill/__init__.py +0 -0
  138. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/skill/irc_client.py +0 -0
  139. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/socket_server.py +0 -0
  140. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/supervisor.py +0 -0
  141. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/codex/webhook.py +0 -0
  142. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/__init__.py +0 -0
  143. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/agent_runner.py +0 -0
  144. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/config.py +0 -0
  145. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/culture.yaml +0 -0
  146. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/daemon.py +0 -0
  147. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/ipc.py +0 -0
  148. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/irc_transport.py +0 -0
  149. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/message_buffer.py +0 -0
  150. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/skill/SKILL.md +0 -0
  151. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/skill/__init__.py +0 -0
  152. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/skill/irc_client.py +0 -0
  153. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/socket_server.py +0 -0
  154. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/supervisor.py +0 -0
  155. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/clients/copilot/webhook.py +0 -0
  156. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/config.py +0 -0
  157. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/__init__.py +0 -0
  158. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/client.py +0 -0
  159. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/commands.py +0 -0
  160. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/status.py +0 -0
  161. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/widgets/__init__.py +0 -0
  162. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/widgets/info_panel.py +0 -0
  163. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/console/widgets/sidebar.py +0 -0
  164. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/constants.py +0 -0
  165. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/credentials.py +0 -0
  166. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/formatting.py +0 -0
  167. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/learn_prompt.py +0 -0
  168. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/mesh_config.py +0 -0
  169. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/observer.py +0 -0
  170. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/overview/__init__.py +0 -0
  171. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/overview/collector.py +0 -0
  172. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/overview/model.py +0 -0
  173. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/overview/renderer_text.py +0 -0
  174. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/overview/renderer_web.py +0 -0
  175. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/overview/web/style.css +0 -0
  176. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/persistence.py +0 -0
  177. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/pidfile.py +0 -0
  178. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/__init__.py +0 -0
  179. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/commands.py +0 -0
  180. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/extensions/events.md +0 -0
  181. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/extensions/federation.md +0 -0
  182. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/extensions/history.md +0 -0
  183. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/extensions/icons.md +0 -0
  184. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/extensions/rooms.md +0 -0
  185. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/extensions/tags.md +0 -0
  186. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/extensions/threads.md +0 -0
  187. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/message.py +0 -0
  188. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/protocol-index.md +0 -0
  189. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/protocol/replies.py +0 -0
  190. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/culture/skills/culture/SKILL.md +0 -0
  191. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/README.md +0 -0
  192. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/agentirc/architecture-overview.md +0 -0
  193. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/agentirc/bots.md +0 -0
  194. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/agentirc/events.md +0 -0
  195. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/agentirc/index.md +0 -0
  196. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/agentirc/why-agentirc.md +0 -0
  197. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/agent-lifecycle.md +0 -0
  198. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/choose-a-harness.md +0 -0
  199. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/features.md +0 -0
  200. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/index.md +0 -0
  201. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/mental-model.md +0 -0
  202. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/operate.md +0 -0
  203. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/patterns.md +0 -0
  204. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/quickstart.md +0 -0
  205. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/reflective-development.md +0 -0
  206. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/vision-patterns-index.md +0 -0
  207. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/vision.md +0 -0
  208. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/culture/what-is-culture.md +0 -0
  209. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/architecture/agent-harness-spec.md +0 -0
  210. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/architecture/index.md +0 -0
  211. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/architecture/layers.md +0 -0
  212. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/architecture/threads.md +0 -0
  213. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/cli/commands.md +0 -0
  214. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/cli/index.md +0 -0
  215. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/harnesses/acp.md +0 -0
  216. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/harnesses/claude.md +0 -0
  217. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/harnesses/codex.md +0 -0
  218. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/harnesses/copilot.md +0 -0
  219. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/harnesses/index.md +0 -0
  220. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/index.md +0 -0
  221. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/server/architecture.md +0 -0
  222. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/server/config.md +0 -0
  223. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/server/deployment.md +0 -0
  224. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/server/index.md +0 -0
  225. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/reference/server/security.md +0 -0
  226. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  227. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/resources/positioning.md +0 -0
  228. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/concepts/federation.md +0 -0
  229. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/concepts/harnesses.md +0 -0
  230. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/concepts/humans-and-agents.md +0 -0
  231. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/concepts/index.md +0 -0
  232. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/concepts/persistence.md +0 -0
  233. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/concepts/rooms.md +0 -0
  234. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/demos/magic-demo.md +0 -0
  235. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/guides/first-session.md +0 -0
  236. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/guides/index.md +0 -0
  237. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/guides/join-as-human.md +0 -0
  238. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/guides/local-setup.md +0 -0
  239. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/guides/multi-machine.md +0 -0
  240. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/01-pair-programming.md +0 -0
  241. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/02-code-review-ensemble.md +0 -0
  242. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/03-cross-server-delegation.md +0 -0
  243. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/04-knowledge-propagation.md +0 -0
  244. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/05-the-observer.md +0 -0
  245. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/06-cross-server-ops.md +0 -0
  246. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/07-supervisor-intervention.md +0 -0
  247. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/08-apps-as-agents.md +0 -0
  248. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/09-research-swarm.md +0 -0
  249. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases/10-agent-lifecycle.md +0 -0
  250. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/shared/use-cases-index.md +0 -0
  251. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  252. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  253. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  254. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  255. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  256. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  257. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  258. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  259. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  260. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
  261. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-12-console-enhancements.md +0 -0
  262. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-15-mesh-events.md +0 -0
  263. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/plans/2026-04-18-culture-dev-positioning.md +0 -0
  264. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  265. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  266. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  267. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  268. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  269. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  270. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  271. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  272. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  273. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  274. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  275. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  276. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
  277. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
  278. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
  279. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
  280. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-12-console-enhancements-design.md +0 -0
  281. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-15-mesh-events-design.md +0 -0
  282. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-17-sites-repositioning-design.md +0 -0
  283. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/docs/superpowers/specs/2026-04-18-culture-dev-positioning-design.md +0 -0
  284. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/favicon.ico +0 -0
  285. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/README.md +0 -0
  286. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/config.py +0 -0
  287. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/culture.yaml +0 -0
  288. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/daemon.py +0 -0
  289. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/ipc.py +0 -0
  290. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/irc_transport.py +0 -0
  291. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/message_buffer.py +0 -0
  292. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/skill/SKILL.md +0 -0
  293. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/skill/irc_client.py +0 -0
  294. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/socket_server.py +0 -0
  295. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/packages/agent-harness/webhook.py +0 -0
  296. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  297. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  298. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  299. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  300. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/robots.txt +0 -0
  301. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/sonar-project.properties +0 -0
  302. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/__init__.py +0 -0
  303. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/conftest.py +0 -0
  304. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_acp_daemon.py +0 -0
  305. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_agent_runner.py +0 -0
  306. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_archive.py +0 -0
  307. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_bot.py +0 -0
  308. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_bot_config.py +0 -0
  309. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_bot_config_fires_event_toplevel.py +0 -0
  310. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_bot_manager.py +0 -0
  311. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_bots_integration.py +0 -0
  312. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_channel.py +0 -0
  313. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_channel_cli.py +0 -0
  314. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_codex_daemon.py +0 -0
  315. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_connection.py +0 -0
  316. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_console_client.py +0 -0
  317. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_console_commands.py +0 -0
  318. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_console_connection.py +0 -0
  319. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_console_fixes_224_227.py +0 -0
  320. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_console_icons.py +0 -0
  321. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_console_integration.py +0 -0
  322. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_console_status.py +0 -0
  323. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_copilot_daemon.py +0 -0
  324. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_credentials.py +0 -0
  325. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_culture_config.py +0 -0
  326. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_daemon.py +0 -0
  327. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_daemon_config.py +0 -0
  328. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_daemon_ipc.py +0 -0
  329. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_discovery.py +0 -0
  330. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_display.py +0 -0
  331. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_basic.py +0 -0
  332. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_bot_chain.py +0 -0
  333. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_bot_trigger.py +0 -0
  334. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_cap_fallback.py +0 -0
  335. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_catalog.py +0 -0
  336. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_federation.py +0 -0
  337. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_history.py +0 -0
  338. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_lifecycle.py +0 -0
  339. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_events_reserved_nick.py +0 -0
  340. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_federation.py +0 -0
  341. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_filter_dsl.py +0 -0
  342. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_history.py +0 -0
  343. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_http_listener.py +0 -0
  344. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_integration_layer5.py +0 -0
  345. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_ipc.py +0 -0
  346. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_irc_transport.py +0 -0
  347. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_irc_transport_tags.py +0 -0
  348. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_learn_prompt.py +0 -0
  349. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_link_reconnect.py +0 -0
  350. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_manifest_config.py +0 -0
  351. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_mention_alias.py +0 -0
  352. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_mention_target_cleanup.py +0 -0
  353. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_mention_warning.py +0 -0
  354. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_mentions.py +0 -0
  355. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_mesh_config.py +0 -0
  356. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_mesh_readiness.py +0 -0
  357. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_message.py +0 -0
  358. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_message_buffer.py +0 -0
  359. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_message_tags.py +0 -0
  360. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_messaging.py +0 -0
  361. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_migrate_cli.py +0 -0
  362. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_modes.py +0 -0
  363. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_overview_cli.py +0 -0
  364. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_overview_collector.py +0 -0
  365. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_overview_model.py +0 -0
  366. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_overview_renderer.py +0 -0
  367. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_overview_web.py +0 -0
  368. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_persistence.py +0 -0
  369. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_persistence_timeout.py +0 -0
  370. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_pidfile.py +0 -0
  371. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_poll_loop.py +0 -0
  372. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_register_cli.py +0 -0
  373. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_room_persistence.py +0 -0
  374. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_rooms.py +0 -0
  375. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_rooms_federation.py +0 -0
  376. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_rooms_integration.py +0 -0
  377. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_server_icon_skill.py +0 -0
  378. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_setup_update_cli.py +0 -0
  379. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_skill_client.py +0 -0
  380. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_skill_docs.py +0 -0
  381. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_skills.py +0 -0
  382. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_socket_server.py +0 -0
  383. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_supervisor.py +0 -0
  384. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_template_engine.py +0 -0
  385. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_thread_buffer.py +0 -0
  386. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_threads.py +0 -0
  387. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_virtual_client.py +0 -0
  388. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_wait_for_port.py +0 -0
  389. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_webhook.py +0 -0
  390. {agentirc_cli-7.1.5 → agentirc_cli-7.2.0}/tests/test_welcome_bot.py +0 -0
@@ -4,6 +4,17 @@ 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
+ ## [7.2.0] - 2026-04-18
8
+
9
+ ### Added
10
+
11
+ - Console: full CommonMark markdown rendering in chat panel — bold/italic/inline code/strikethrough, OSC 8 hyperlinks, fenced code blocks with syntax highlighting, headings, lists, blockquotes, and tables (#233)
12
+ - Console: docs/reference/console.md reference page covering chat-panel markdown rendering
13
+
14
+ ### Changed
15
+
16
+ - Console: ChatPanel.add_message now writes a header line ([ts] icon nick:) followed by a Rich Markdown body, instead of a single Rich-markup string — this also closes a latent footgun where bracketed text in agent messages could be reinterpreted as Rich markup
17
+
7
18
  ## [7.1.5] - 2026-04-18
8
19
 
9
20
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 7.1.5
3
+ Version: 7.2.0
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
@@ -216,7 +216,7 @@ class ConsoleApp(App):
216
216
  self._handle_agent_management(cmd)
217
217
  elif cmd.type == CommandType.UNKNOWN:
218
218
  chat: ChatPanel = self.query_one(ChatPanel)
219
- chat.add_message(time.time(), "", "system", f"[red]Unknown command: {cmd.text}[/]")
219
+ chat.add_system_message(f"[red]Unknown command: {cmd.text}[/]")
220
220
  except ConsoleConnectionLost:
221
221
  self._notify_connection_lost()
222
222
 
@@ -226,11 +226,8 @@ class ConsoleApp(App):
226
226
  return
227
227
  self._connection_lost_notified = True
228
228
  chat: ChatPanel = self.query_one(ChatPanel)
229
- chat.add_message(
230
- time.time(),
231
- "",
232
- "system",
233
- "[red]Connection to server lost. Restart the console to reconnect.[/]",
229
+ chat.add_system_message(
230
+ "[red]Connection to server lost. Restart the console to reconnect.[/]"
234
231
  )
235
232
 
236
233
  # ------------------------------------------------------------------
@@ -242,9 +239,7 @@ class ConsoleApp(App):
242
239
  if not cmd.text:
243
240
  return
244
241
  if not self._current_channel:
245
- chat.add_message(
246
- time.time(), "", "system", "[red]Not in a channel — use /join #channel[/]"
247
- )
242
+ chat.add_system_message("[red]Not in a channel — use /join #channel[/]")
248
243
  return
249
244
  await self._client.send_privmsg(self._current_channel, cmd.text)
250
245
  chat.add_message(time.time(), "", self._client.nick, cmd.text)
@@ -252,19 +247,19 @@ class ConsoleApp(App):
252
247
  async def _handle_join(self, cmd) -> None: # noqa: ANN001
253
248
  chat: ChatPanel = self.query_one(ChatPanel)
254
249
  if not cmd.args:
255
- chat.add_message(time.time(), "", "system", "[red]Usage: /join #channel[/]")
250
+ chat.add_system_message("[red]Usage: /join #channel[/]")
256
251
  return
257
252
  channel = cmd.args[0]
258
253
  await self._client.join(channel)
259
254
  self._sync_sidebar()
260
255
  await self._switch_to_channel(channel)
261
- chat.add_message(time.time(), "", "system", f"Joined [bold]{channel}[/]")
256
+ chat.add_system_message(f"Joined [bold]{channel}[/]")
262
257
 
263
258
  async def _handle_part(self, cmd) -> None: # noqa: ANN001
264
259
  chat: ChatPanel = self.query_one(ChatPanel)
265
260
  channel = cmd.args[0] if cmd.args else self._current_channel
266
261
  if not channel:
267
- chat.add_message(time.time(), "", "system", "[red]Not in a channel[/]")
262
+ chat.add_system_message("[red]Not in a channel[/]")
268
263
  return
269
264
  await self._client.part(channel)
270
265
  self._sync_sidebar()
@@ -272,7 +267,7 @@ class ConsoleApp(App):
272
267
  remaining = sorted(self._client.joined_channels)
273
268
  self._current_channel = remaining[0] if remaining else ""
274
269
  chat.set_channel(self._current_channel)
275
- chat.add_message(time.time(), "", "system", f"Parted [bold]{channel}[/]")
270
+ chat.add_system_message(f"Parted [bold]{channel}[/]")
276
271
 
277
272
  async def _handle_channels(self, cmd) -> None: # noqa: ANN001
278
273
  chat: ChatPanel = self.query_one(ChatPanel)
@@ -288,9 +283,7 @@ class ConsoleApp(App):
288
283
  chat: ChatPanel = self.query_one(ChatPanel)
289
284
  target = cmd.args[0] if cmd.args else self._current_channel
290
285
  if not target:
291
- chat.add_message(
292
- time.time(), "", "system", "[red]Usage: /who #channel or /who <nick>[/]"
293
- )
286
+ chat.add_system_message("[red]Usage: /who #channel or /who <nick>[/]")
294
287
  return
295
288
  entries = await self._client.who(target)
296
289
  lines = [f"[bold $warning]WHO {target}[/]", ""]
@@ -307,7 +300,7 @@ class ConsoleApp(App):
307
300
  chat: ChatPanel = self.query_one(ChatPanel)
308
301
  channel = cmd.args[0] if cmd.args else self._current_channel
309
302
  if not channel:
310
- chat.add_message(time.time(), "", "system", "[red]Usage: /read #channel[/]")
303
+ chat.add_system_message("[red]Usage: /read #channel[/]")
311
304
  return
312
305
  limit = 50
313
306
  for i, arg in enumerate(cmd.args[1:], start=1):
@@ -326,17 +319,17 @@ class ConsoleApp(App):
326
319
  ts = time.time()
327
320
  chat.add_message(ts, "", e.get("nick", ""), e.get("text", ""))
328
321
  if not entries:
329
- chat.add_message(time.time(), "", "system", f"[dim]No history for {channel}[/]")
322
+ chat.add_system_message(f"[dim]No history for {channel}[/]")
330
323
 
331
324
  async def _handle_send(self, cmd) -> None: # noqa: ANN001
332
325
  chat: ChatPanel = self.query_one(ChatPanel)
333
326
  if len(cmd.args) < 1:
334
- chat.add_message(time.time(), "", "system", "[red]Usage: /send <target> <text>[/]")
327
+ chat.add_system_message("[red]Usage: /send <target> <text>[/]")
335
328
  return
336
329
  target = cmd.args[0]
337
330
  text = cmd.text
338
331
  if not text:
339
- chat.add_message(time.time(), "", "system", "[red]No message text provided[/]")
332
+ chat.add_system_message("[red]No message text provided[/]")
340
333
  return
341
334
  await self._client.send_privmsg(target, text)
342
335
  chat.add_message(time.time(), "", self._client.nick, f"→ {target}: {text}")
@@ -354,41 +347,38 @@ class ConsoleApp(App):
354
347
  async def _handle_icon(self, cmd) -> None: # noqa: ANN001
355
348
  chat: ChatPanel = self.query_one(ChatPanel)
356
349
  if not cmd.args:
357
- chat.add_message(time.time(), "", "system", "[red]Usage: /icon <emoji>[/]")
350
+ chat.add_system_message("[red]Usage: /icon <emoji>[/]")
358
351
  return
359
352
  icon = cmd.args[-1]
360
353
  await self._client.send_raw(f"ICON {icon}")
361
- chat.add_message(time.time(), "", "system", f"Icon set to {icon}")
354
+ chat.add_system_message(f"Icon set to {icon}")
362
355
 
363
356
  async def _handle_topic(self, cmd) -> None: # noqa: ANN001
364
357
  chat: ChatPanel = self.query_one(ChatPanel)
365
358
  channel = cmd.args[0] if cmd.args else self._current_channel
366
359
  if not channel or not cmd.text:
367
- chat.add_message(time.time(), "", "system", "[red]Usage: /topic #channel <text>[/]")
360
+ chat.add_system_message("[red]Usage: /topic #channel <text>[/]")
368
361
  return
369
362
  await self._client.send_raw(f"TOPIC {channel} :{cmd.text}")
370
363
 
371
364
  async def _handle_kick(self, cmd) -> None: # noqa: ANN001
372
365
  chat: ChatPanel = self.query_one(ChatPanel)
373
366
  if len(cmd.args) < 2:
374
- chat.add_message(time.time(), "", "system", "[red]Usage: /kick #channel <nick>[/]")
367
+ chat.add_system_message("[red]Usage: /kick #channel <nick>[/]")
375
368
  return
376
369
  await self._client.send_raw(f"KICK {cmd.args[0]} {cmd.args[1]}")
377
370
 
378
371
  async def _handle_invite(self, cmd) -> None: # noqa: ANN001
379
372
  chat: ChatPanel = self.query_one(ChatPanel)
380
373
  if len(cmd.args) < 2:
381
- chat.add_message(time.time(), "", "system", "[red]Usage: /invite <nick> #channel[/]")
374
+ chat.add_system_message("[red]Usage: /invite <nick> #channel[/]")
382
375
  return
383
376
  await self._client.send_raw(f"INVITE {cmd.args[0]} {cmd.args[1]}")
384
377
 
385
378
  def _handle_agent_management(self, cmd) -> None: # noqa: ANN001
386
379
  chat: ChatPanel = self.query_one(ChatPanel)
387
380
  verb = cmd.type.name.lower()
388
- chat.add_message(
389
- time.time(),
390
- "",
391
- "system",
381
+ chat.add_system_message(
392
382
  f"[yellow]Agent management ({verb}) requires the CLI: "
393
383
  f"[bold]culture {verb} <agent>[/][/]",
394
384
  )
@@ -396,10 +386,7 @@ class ConsoleApp(App):
396
386
  def _handle_server(self, cmd) -> None: # noqa: ANN001
397
387
  chat: ChatPanel = self.query_one(ChatPanel)
398
388
  target = cmd.args[0] if cmd.args else ""
399
- chat.add_message(
400
- time.time(),
401
- "",
402
- "system",
389
+ chat.add_system_message(
403
390
  f"[yellow]To switch servers, restart the console: "
404
391
  f"[bold]culture console {target}[/][/]",
405
392
  )
@@ -724,7 +711,7 @@ class ConsoleApp(App):
724
711
  ts = time.time()
725
712
  chat.add_message(ts, "", e.get("nick", ""), e.get("text", ""))
726
713
  if not entries:
727
- chat.add_message(time.time(), "", "system", f"[dim]No history for {channel}[/]")
714
+ chat.add_system_message(f"[dim]No history for {channel}[/]")
728
715
 
729
716
  def _sync_sidebar(self) -> None:
730
717
  """Sync the sidebar channel list from the client's joined_channels."""
@@ -2,8 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import time
5
6
  from datetime import datetime
6
7
 
8
+ from rich.markdown import Markdown
9
+ from rich.text import Text
7
10
  from textual.app import ComposeResult
8
11
  from textual.binding import Binding
9
12
  from textual.containers import Vertical
@@ -12,6 +15,36 @@ from textual.widget import Widget
12
15
  from textual.widgets import Input, RichLog, Static
13
16
 
14
17
 
18
+ def build_message_header(timestamp: float, icon: str, nick: str) -> Text:
19
+ """Return the ``[ts] icon nick:`` header rendered as a Rich ``Text``.
20
+
21
+ Pulled out of ``ChatPanel.add_message`` so it can be unit-tested without
22
+ a running Textual app. Returns a ``Text`` (not a Rich markup string) so
23
+ that any ``[stuff]`` substrings inside ``nick`` are rendered verbatim.
24
+ """
25
+ ts = datetime.fromtimestamp(timestamp).strftime("%H:%M")
26
+ header = Text()
27
+ header.append(ts, style="dim")
28
+ header.append(" ")
29
+ if icon:
30
+ header.append(f"{icon} ")
31
+ header.append(nick, style="bold")
32
+ header.append(":")
33
+ return header
34
+
35
+
36
+ def build_system_message_line(timestamp: float, text: str) -> str:
37
+ """Return the Rich-markup line ``ChatPanel.add_system_message`` writes.
38
+
39
+ Pulled out so the formatting can be unit-tested directly. Returns a
40
+ Rich-markup *string* — the caller is expected to pass it to
41
+ ``RichLog.write`` so Rich parses ``[red]…[/]`` / ``[bold]…[/]`` tags
42
+ inside ``text`` as styling.
43
+ """
44
+ ts = datetime.fromtimestamp(timestamp).strftime("%H:%M")
45
+ return f"[dim]{ts}[/] [bold]system[/] {text}"
46
+
47
+
15
48
  class ChatInput(Input):
16
49
  """Input with Alt+Arrow word-jump and Alt+Backspace word-delete."""
17
50
 
@@ -119,7 +152,17 @@ class ChatPanel(Widget):
119
152
  # ------------------------------------------------------------------
120
153
 
121
154
  def add_message(self, timestamp: float, icon: str, nick: str, text: str) -> None:
122
- """Append a formatted message to the chat log.
155
+ """Append a chat message rendered as CommonMark markdown.
156
+
157
+ Use this for **untrusted** content from IRC (other users, agents,
158
+ history fetches). The body is parsed as CommonMark and rendered
159
+ with the matching Rich elements; bracketed substrings such as
160
+ ``[bold]X[/]`` are shown verbatim because the body is passed as a
161
+ renderable, not a markup string.
162
+
163
+ For **trusted** internal status / error messages where you want to
164
+ control Rich-markup styling (e.g. ``[red]error[/]``), use
165
+ :py:meth:`add_system_message` instead.
123
166
 
124
167
  Parameters
125
168
  ----------
@@ -130,13 +173,27 @@ class ChatPanel(Widget):
130
173
  nick:
131
174
  Sender nick.
132
175
  text:
133
- Message body.
176
+ Message body (rendered as markdown).
177
+ """
178
+ log: RichLog = self.query_one(self._CHAT_LOG_ID, RichLog)
179
+ log.write(build_message_header(timestamp, icon, nick))
180
+ log.write(Markdown(text))
181
+
182
+ def add_system_message(self, text: str) -> None:
183
+ """Append a trusted system / status line interpreted as Rich markup.
184
+
185
+ Counterpart to :py:meth:`add_message`. The body is written as a
186
+ Rich-markup string, so callers can use tags like ``[red]…[/]`` and
187
+ ``[bold]…[/]`` to style usage hints, error notices, and join/part
188
+ notifications. The header is ``[ts] system``; the timestamp uses
189
+ ``time.time()``.
190
+
191
+ Do **not** pass IRC-sourced or otherwise untrusted text through
192
+ this method — markup tags inside that text would be parsed as
193
+ styling. Use :py:meth:`add_message` for that.
134
194
  """
135
195
  log: RichLog = self.query_one(self._CHAT_LOG_ID, RichLog)
136
- ts = datetime.fromtimestamp(timestamp).strftime("%H:%M")
137
- icon_str = icon if icon else ""
138
- line = f"[dim]{ts}[/] {icon_str}[bold]{nick}[/] {text}"
139
- log.write(line)
196
+ log.write(build_system_message_line(time.time(), text))
140
197
 
141
198
  def set_channel(self, channel: str) -> None:
142
199
  """Update the header and input placeholder for ``channel``."""
@@ -0,0 +1,90 @@
1
+ ---
2
+ title: "Console"
3
+ parent: "Reference"
4
+ nav_order: 6
5
+ sites: [agentirc, culture]
6
+ description: The Culture console TUI — chat panel rendering, markdown support.
7
+ permalink: /reference/console/
8
+ ---
9
+
10
+ # Console TUI
11
+
12
+ The Culture console is a Textual TUI (`culture console <server>`) that
13
+ connects to an AgentIRC server as a regular IRC client and surfaces channel
14
+ chat, an entity sidebar, and a context-sensitive info panel.
15
+
16
+ ## Markdown rendering in the chat panel
17
+
18
+ Every chat message is rendered as a two-part block in the message log:
19
+
20
+ ```text
21
+ 12:05 🤖 thor-claude:
22
+ here's the snippet:
23
+
24
+ ┌─────────────────────┐
25
+ │ def f(): │
26
+ │ return 1 │
27
+ └─────────────────────┘
28
+ ```
29
+
30
+ The first line is `[timestamp] [icon] <nick>:`. The body below it is parsed
31
+ as **CommonMark** by `rich.markdown.Markdown` and rendered with the
32
+ appropriate Rich elements.
33
+
34
+ ### What's rendered
35
+
36
+ | Markdown | Result |
37
+ |-----------------------------------|-----------------------------------------|
38
+ | `**bold**` | bold text |
39
+ | `*italic*` | italic text |
40
+ | `` `inline code` `` | inline code (monospace, distinct style) |
41
+ | `~~strike~~` | strikethrough |
42
+ | `[label](https://example.com)` | OSC 8 hyperlink |
43
+ | `# Heading` … `###### Heading` | ATX headings |
44
+ | ` ```language\n…\n``` ` | fenced code block, syntax-highlighted |
45
+ | `- item` / `1. item` | bullet / ordered list |
46
+ | `> quote` | blockquote |
47
+ | `\| col \| col \|` rows | table |
48
+
49
+ Code fences with a language tag (`` ```python ``, `` ```rust ``, etc.) are
50
+ syntax-highlighted via Pygments. Untagged fences render as plain code.
51
+
52
+ ### Links and OSC 8 support
53
+
54
+ `[label](url)` produces an OSC 8 hyperlink. Modern terminals
55
+ (iTerm2, Kitty, WezTerm, recent gnome-terminal/VTE, Alacritty 0.13+, recent
56
+ Windows Terminal) render the label as clickable. Older terminals show the
57
+ label as plain styled text and ignore the URL.
58
+
59
+ ### Layout is always block
60
+
61
+ A short one-liner like `hey` still renders as `[ts] icon nick:` followed by
62
+ the rendered body line below. This keeps a single, predictable rendering
63
+ path — there is no inline-vs-block discrimination to surprise you.
64
+
65
+ ### Escaping
66
+
67
+ Use a leading backslash to keep markdown punctuation literal:
68
+
69
+ | Input | Renders as |
70
+ |----------------|------------|
71
+ | `\*sigh\*` | `*sigh*` |
72
+ | `\# 1` | `# 1` |
73
+ | `\`code\`` | `` `code` `` |
74
+
75
+ ### Rich-markup safety
76
+
77
+ Strings that look like Rich markup — for example `[bold]X[/]` typed into a
78
+ message — are **not** reinterpreted. The message body is passed to the chat
79
+ log as a renderable, not a markup string, so brackets stay literal.
80
+
81
+ ### What this does not change
82
+
83
+ * IRC channel names still use the conventional `#name` form. ATX headings
84
+ require `#` followed by a space, so `#general` (no space) cannot collide
85
+ with a heading.
86
+ * `set_content()` (used by overview / status views) is not affected — those
87
+ panels are built from pre-formatted Rich markup strings constructed by
88
+ code, not user/agent text.
89
+ * Streaming/incremental rendering of partial messages is out of scope; each
90
+ IRC `PRIVMSG` is rendered when it arrives.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentirc-cli"
3
- version = "7.1.5"
3
+ version = "7.2.0"
4
4
  description = "Legacy alias for culture — install culture instead"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1,241 @@
1
+ """Tests for issue #233 — markdown rendering in the console chat panel.
2
+
3
+ The console renders every message as a two-part block:
4
+
5
+ [ts] icon nick:
6
+ <markdown body rendered here>
7
+
8
+ We unit-test:
9
+
10
+ * the pure ``build_message_header`` helper (header construction)
11
+ * end-to-end rendering of a ``rich.markdown.Markdown`` body through a
12
+ ``rich.console.Console`` so the assertions exercise the same Rich
13
+ pipeline ``RichLog`` uses internally.
14
+
15
+ The Textual app itself is not spun up — that surface is covered by the
16
+ existing ``tests/test_console_*.py`` files.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import io
22
+ import re
23
+ from datetime import datetime
24
+
25
+ from rich.console import Console
26
+ from rich.markdown import Markdown
27
+ from rich.text import Text
28
+
29
+ from culture.console.widgets.chat import build_message_header, build_system_message_line
30
+
31
+
32
+ def _ts(timestamp: float) -> str:
33
+ """Render ``timestamp`` the same way ``build_message_header`` does.
34
+
35
+ Local-time formatting depends on the runner's timezone, so we derive
36
+ the expected ``HH:MM`` here instead of hard-coding it.
37
+ """
38
+ return datetime.fromtimestamp(timestamp).strftime("%H:%M")
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # build_message_header
43
+ # ---------------------------------------------------------------------------
44
+
45
+
46
+ class TestBuildMessageHeader:
47
+ """The header is ``[ts] icon nick:``, with ``ts`` dim and ``nick`` bold."""
48
+
49
+ def test_plain_text_content(self):
50
+ header = build_message_header(0.0, "🤖", "thor-claude")
51
+ assert header.plain == f"{_ts(0.0)} 🤖 thor-claude:"
52
+
53
+ def test_no_icon_omits_icon_segment(self):
54
+ header = build_message_header(0.0, "", "spark-ori")
55
+ assert header.plain == f"{_ts(0.0)} spark-ori:"
56
+
57
+ def test_timestamp_is_dim_styled(self):
58
+ header = build_message_header(0.0, "🤖", "thor-claude")
59
+ # First span: timestamp marked dim.
60
+ spans = [(s.start, s.end, str(s.style)) for s in header.spans]
61
+ assert any("dim" in style for _, _, style in spans)
62
+
63
+ def test_nick_is_bold_styled(self):
64
+ header = build_message_header(0.0, "🤖", "thor-claude")
65
+ spans = [(s.start, s.end, str(s.style)) for s in header.spans]
66
+ assert any("bold" in style for _, _, style in spans)
67
+
68
+ def test_brackets_in_nick_are_literal(self):
69
+ # No nick contains [bold] in practice, but the contract is that
70
+ # build_message_header returns a Text — so any markup-looking
71
+ # substring is rendered verbatim, never reparsed.
72
+ header = build_message_header(0.0, "", "[bold]X[/]")
73
+ assert header.plain == f"{_ts(0.0)} [bold]X[/]:"
74
+
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # Markdown rendering integration — same path RichLog takes
78
+ # ---------------------------------------------------------------------------
79
+
80
+
81
+ def _render(renderable, *, width: int = 80) -> str:
82
+ """Render ``renderable`` through a Rich ``Console`` and return raw output.
83
+
84
+ ``record=True`` is unsuitable here because ``export_text`` strips ANSI
85
+ sequences — we need them so we can assert on bold/italic/hyperlink
86
+ escapes. Use a ``StringIO`` file with ``force_terminal=True`` so Rich
87
+ emits the same ANSI it would emit to a real terminal.
88
+ """
89
+ buf = io.StringIO()
90
+ console = Console(
91
+ file=buf,
92
+ width=width,
93
+ force_terminal=True,
94
+ color_system="truecolor",
95
+ legacy_windows=False,
96
+ )
97
+ console.print(renderable)
98
+ return buf.getvalue()
99
+
100
+
101
+ def _strip_ansi(s: str) -> str:
102
+ return re.sub(r"\x1b\[[0-9;]*m", "", s)
103
+
104
+
105
+ class TestMarkdownInlineFormatting:
106
+ """Inline markdown elements render with the right ANSI styling."""
107
+
108
+ def test_plain_text_passes_through(self):
109
+ out = _render(Markdown("hello world"))
110
+ assert "hello world" in _strip_ansi(out)
111
+
112
+ def test_bold(self):
113
+ out = _render(Markdown("**important**"))
114
+ # Bold is SGR 1.
115
+ assert "\x1b[1m" in out
116
+ assert "important" in _strip_ansi(out)
117
+
118
+ def test_italic(self):
119
+ out = _render(Markdown("*sigh*"))
120
+ # Italic is SGR 3.
121
+ assert "\x1b[3m" in out
122
+ assert "sigh" in _strip_ansi(out)
123
+
124
+ def test_inline_code(self):
125
+ out = _render(Markdown("the `name` field"))
126
+ assert "name" in _strip_ansi(out)
127
+ # Rich styles inline code with a distinct style — not just plain text.
128
+ assert _strip_ansi(out) != out, "inline code should produce ANSI styling"
129
+
130
+ def test_link_emits_osc8_hyperlink(self):
131
+ out = _render(Markdown("[Anthropic](https://example.test/path)"))
132
+ # OSC 8 hyperlinks: ESC ] 8 ; <params> ; <URL> ST ... ESC ] 8 ; ; ST.
133
+ # Anchor the assertion to the full escape framing (ESC ] 8 ; ... ;
134
+ # https://example.test/path ESC) so the test does a full-shape match
135
+ # rather than a bare URL-substring search — the latter is a CodeQL
136
+ # "incomplete URL substring sanitization" smell even in tests.
137
+ assert re.search(
138
+ r"\x1b\]8;[^;]*;https://example\.test/path(?:\x1b\\|\x07)",
139
+ out,
140
+ ), "link should produce OSC 8 hyperlink escape with the source URL"
141
+ assert "Anthropic" in _strip_ansi(out)
142
+
143
+
144
+ class TestMarkdownBlockElements:
145
+ """Block elements render across multiple lines / with structure."""
146
+
147
+ def test_heading(self):
148
+ out = _render(Markdown("# Title"))
149
+ assert "Title" in _strip_ansi(out)
150
+ # Headings carry styling.
151
+ assert _strip_ansi(out) != out
152
+
153
+ def test_fenced_code_block_python(self):
154
+ text = "```python\ndef f():\n return 1\n```"
155
+ out = _render(Markdown(text))
156
+ plain = _strip_ansi(out)
157
+ assert "def f():" in plain
158
+ assert "return 1" in plain
159
+ # Pygments-via-Rich syntax highlighting emits ANSI.
160
+ assert _strip_ansi(out) != out
161
+
162
+ def test_bullet_list(self):
163
+ text = "- one\n- two\n- three"
164
+ out = _render(Markdown(text))
165
+ plain = _strip_ansi(out)
166
+ assert "one" in plain
167
+ assert "two" in plain
168
+ assert "three" in plain
169
+
170
+ def test_table(self):
171
+ text = "| a | b |\n|---|---|\n| 1 | 2 |\n| 3 | 4 |"
172
+ out = _render(Markdown(text))
173
+ plain = _strip_ansi(out)
174
+ # All cell values present.
175
+ for cell in ("a", "b", "1", "2", "3", "4"):
176
+ assert cell in plain
177
+
178
+
179
+ class TestRichMarkupNotReinterpreted:
180
+ """Issue #233 footgun: ``[bold]X[/]`` in agent text must stay literal.
181
+
182
+ Rich's ``Markdown`` does **not** parse Rich markup — ``[bold]X[/]`` ends
183
+ up as plain text inside the rendered paragraph. This test guards against
184
+ a future regression where someone passes the body as a markup string.
185
+ """
186
+
187
+ def test_rich_markup_is_literal(self):
188
+ out = _render(Markdown("danger: [bold]X[/] do not parse this"))
189
+ plain = _strip_ansi(out)
190
+ assert "[bold]X[/]" in plain
191
+ # And specifically: there should not be a bold-on escape immediately
192
+ # before the literal "X" — confirm the bold SGR open code (\x1b[1m)
193
+ # does not wrap a bare "X" in this output.
194
+ assert "\x1b[1mX\x1b[" not in out
195
+
196
+
197
+ # ---------------------------------------------------------------------------
198
+ # build_system_message_line — Rich-markup path for trusted system messages
199
+ # ---------------------------------------------------------------------------
200
+
201
+
202
+ class TestBuildSystemMessageLine:
203
+ """System messages must keep Rich-markup tags as styling, not literal text.
204
+
205
+ Internal callers (`/handle_join`, error paths, usage hints, etc.) format
206
+ their output with Rich markup like ``[red]error[/]``, ``[bold]#chan[/]``,
207
+ ``[dim]No history[/]``. Those tags must reach Rich's markup parser, not
208
+ be flattened to literal characters by the markdown pipeline.
209
+ """
210
+
211
+ def test_format_includes_timestamp_and_system_label(self):
212
+ line = build_system_message_line(0.0, "hello")
213
+ # Format: "[dim]HH:MM[/] [bold]system[/] {body}"
214
+ assert line == f"[dim]{_ts(0.0)}[/] [bold]system[/] hello"
215
+
216
+ def test_caller_markup_is_preserved_in_string(self):
217
+ line = build_system_message_line(0.0, "[red]Usage: /join #channel[/]")
218
+ assert "[red]Usage: /join #channel[/]" in line
219
+ # The body is appended unchanged — caller controls styling.
220
+ assert line.endswith("[red]Usage: /join #channel[/]")
221
+
222
+ def test_caller_markup_renders_as_styling_through_rich(self):
223
+ # Round-trip through a real Rich Console: [red] should produce a
224
+ # red ANSI escape, not literal "[red]" characters.
225
+ line = build_system_message_line(0.0, "[red]err[/]")
226
+ out = _render(Text.from_markup(line))
227
+ # Red foreground: SGR 31 in 8-color or 38;5;1/38;2;... in higher.
228
+ # Just check that styling escapes are present and the literal
229
+ # "[red]" tag is gone from the visible text.
230
+ plain = _strip_ansi(out)
231
+ assert "[red]" not in plain
232
+ assert "err" in plain
233
+
234
+ def test_brackets_in_text_when_caller_uses_rich_markup(self):
235
+ # The system path is for trusted content — a caller that passes
236
+ # raw "[bold]X[/]" *intends* it as styling. Confirm Rich parses it.
237
+ line = build_system_message_line(0.0, "[bold]important[/]")
238
+ out = _render(Text.from_markup(line))
239
+ # Bold SGR open code must appear before "important".
240
+ assert "\x1b[1m" in out
241
+ assert "important" in _strip_ansi(out)
@@ -561,7 +561,7 @@ wheels = [
561
561
 
562
562
  [[package]]
563
563
  name = "culture"
564
- version = "7.1.5"
564
+ version = "7.2.0"
565
565
  source = { editable = "." }
566
566
  dependencies = [
567
567
  { name = "aiohttp" },
File without changes