agentirc-cli 3.0.0__tar.gz → 3.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 (295) hide show
  1. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/CHANGELOG.md +15 -0
  2. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/CLAUDE.md +1 -1
  3. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/PKG-INFO +1 -1
  4. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/cli.py +91 -19
  5. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/overview/collector.py +28 -21
  6. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/ircd.py +20 -6
  7. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/pyproject.toml +1 -1
  8. agentirc_cli-3.0.2/tests/test_overview_cli.py +133 -0
  9. agentirc_cli-3.0.2/tests/test_wait_for_port.py +89 -0
  10. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/uv.lock +1 -1
  11. agentirc_cli-3.0.0/tests/test_overview_cli.py +0 -42
  12. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.claude/skills/pr-review/SKILL.md +0 -0
  13. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.claude/skills/run-tests/SKILL.md +0 -0
  14. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.claude/skills/run-tests/scripts/test.sh +0 -0
  15. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.flake8 +0 -0
  16. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.github/workflows/pages.yml +0 -0
  17. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.github/workflows/publish.yml +0 -0
  18. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.github/workflows/security-checks.yml +0 -0
  19. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.github/workflows/tests.yml +0 -0
  20. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.gitignore +0 -0
  21. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.markdownlint-cli2.yaml +0 -0
  22. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.pr_agent.toml +0 -0
  23. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.pre-commit-config.yaml +0 -0
  24. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/.pylintrc +0 -0
  25. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/CNAME +0 -0
  26. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/Gemfile +0 -0
  27. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/Gemfile.lock +0 -0
  28. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/LICENSE +0 -0
  29. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/README.md +0 -0
  30. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/SECURITY.md +0 -0
  31. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/_config.yml +0 -0
  32. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/_sass/color_schemes/anthropic.scss +0 -0
  33. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/_sass/custom/custom.scss +0 -0
  34. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/__init__.py +0 -0
  35. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/__main__.py +0 -0
  36. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/bots/__init__.py +0 -0
  37. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/bots/bot.py +0 -0
  38. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/bots/bot_manager.py +0 -0
  39. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/bots/config.py +0 -0
  40. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/bots/http_listener.py +0 -0
  41. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/bots/template_engine.py +0 -0
  42. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/bots/virtual_client.py +0 -0
  43. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/__init__.py +0 -0
  44. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/__init__.py +0 -0
  45. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/agent_runner.py +0 -0
  46. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/config.py +0 -0
  47. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/daemon.py +0 -0
  48. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/ipc.py +0 -0
  49. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/irc_transport.py +0 -0
  50. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/message_buffer.py +0 -0
  51. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/skill/SKILL.md +0 -0
  52. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/skill/__init__.py +0 -0
  53. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/skill/irc_client.py +0 -0
  54. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/socket_server.py +0 -0
  55. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/supervisor.py +0 -0
  56. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/acp/webhook.py +0 -0
  57. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/__init__.py +0 -0
  58. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/__main__.py +0 -0
  59. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/agent_runner.py +0 -0
  60. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/config.py +0 -0
  61. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/daemon.py +0 -0
  62. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/ipc.py +0 -0
  63. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/irc_transport.py +0 -0
  64. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/message_buffer.py +0 -0
  65. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/skill/SKILL.md +0 -0
  66. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/skill/__init__.py +0 -0
  67. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/skill/irc_client.py +0 -0
  68. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/socket_server.py +0 -0
  69. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/supervisor.py +0 -0
  70. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/claude/webhook.py +0 -0
  71. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/__init__.py +0 -0
  72. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/agent_runner.py +0 -0
  73. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/config.py +0 -0
  74. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/daemon.py +0 -0
  75. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/ipc.py +0 -0
  76. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/irc_transport.py +0 -0
  77. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/message_buffer.py +0 -0
  78. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/skill/SKILL.md +0 -0
  79. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/skill/__init__.py +0 -0
  80. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/skill/irc_client.py +0 -0
  81. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/socket_server.py +0 -0
  82. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/supervisor.py +0 -0
  83. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/codex/webhook.py +0 -0
  84. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/__init__.py +0 -0
  85. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/agent_runner.py +0 -0
  86. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/config.py +0 -0
  87. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/daemon.py +0 -0
  88. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/ipc.py +0 -0
  89. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/irc_transport.py +0 -0
  90. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/message_buffer.py +0 -0
  91. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/skill/SKILL.md +0 -0
  92. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/skill/__init__.py +0 -0
  93. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/skill/irc_client.py +0 -0
  94. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/socket_server.py +0 -0
  95. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/supervisor.py +0 -0
  96. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/clients/copilot/webhook.py +0 -0
  97. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/console/__init__.py +0 -0
  98. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/console/app.py +0 -0
  99. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/console/client.py +0 -0
  100. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/console/commands.py +0 -0
  101. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/console/widgets/__init__.py +0 -0
  102. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/console/widgets/chat.py +0 -0
  103. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/console/widgets/info_panel.py +0 -0
  104. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/console/widgets/sidebar.py +0 -0
  105. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/credentials.py +0 -0
  106. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/learn_prompt.py +0 -0
  107. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/mesh_config.py +0 -0
  108. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/observer.py +0 -0
  109. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/overview/__init__.py +0 -0
  110. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/overview/model.py +0 -0
  111. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/overview/renderer_text.py +0 -0
  112. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/overview/renderer_web.py +0 -0
  113. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/overview/web/style.css +0 -0
  114. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/persistence.py +0 -0
  115. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/pidfile.py +0 -0
  116. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/__init__.py +0 -0
  117. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/commands.py +0 -0
  118. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/extensions/federation.md +0 -0
  119. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/extensions/history.md +0 -0
  120. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/extensions/icons.md +0 -0
  121. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/extensions/rooms.md +0 -0
  122. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/extensions/tags.md +0 -0
  123. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/extensions/threads.md +0 -0
  124. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/message.py +0 -0
  125. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/protocol-index.md +0 -0
  126. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/protocol/replies.py +0 -0
  127. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/__init__.py +0 -0
  128. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/__main__.py +0 -0
  129. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/channel.py +0 -0
  130. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/client.py +0 -0
  131. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/config.py +0 -0
  132. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/remote_client.py +0 -0
  133. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/room_store.py +0 -0
  134. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/rooms_util.py +0 -0
  135. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/server_link.py +0 -0
  136. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/skill.py +0 -0
  137. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/skills/__init__.py +0 -0
  138. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/skills/history.py +0 -0
  139. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/skills/icon.py +0 -0
  140. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/skills/rooms.py +0 -0
  141. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/skills/threads.py +0 -0
  142. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/server/thread_store.py +0 -0
  143. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/culture/skills/culture/SKILL.md +0 -0
  144. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/agent-lifecycle.md +0 -0
  145. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/agentic-self-learn.md +0 -0
  146. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/agent-client.md +0 -0
  147. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/agent-harness-spec.md +0 -0
  148. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/design.md +0 -0
  149. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/harness-conformance.md +0 -0
  150. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/index.md +0 -0
  151. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/layer1-core-irc.md +0 -0
  152. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/layer2-attention.md +0 -0
  153. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/layer3-skills.md +0 -0
  154. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/layer4-federation.md +0 -0
  155. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/layer5-agent-harness.md +0 -0
  156. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/server-architecture.md +0 -0
  157. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/architecture/threads.md +0 -0
  158. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/acp/overview.md +0 -0
  159. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/claude/configuration.md +0 -0
  160. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/claude/context-management.md +0 -0
  161. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/claude/irc-tools.md +0 -0
  162. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/claude/overview.md +0 -0
  163. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/claude/setup.md +0 -0
  164. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/claude/supervisor.md +0 -0
  165. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/claude/webhooks.md +0 -0
  166. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/codex/configuration.md +0 -0
  167. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/codex/context-management.md +0 -0
  168. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/codex/irc-tools.md +0 -0
  169. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/codex/overview.md +0 -0
  170. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/codex/setup.md +0 -0
  171. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/codex/supervisor.md +0 -0
  172. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/codex/webhooks.md +0 -0
  173. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/copilot/configuration.md +0 -0
  174. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/copilot/context-management.md +0 -0
  175. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/copilot/irc-tools.md +0 -0
  176. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/copilot/overview.md +0 -0
  177. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/copilot/setup.md +0 -0
  178. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/copilot/supervisor.md +0 -0
  179. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/clients/copilot/webhooks.md +0 -0
  180. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/culture-cli.md +0 -0
  181. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/getting-started.md +0 -0
  182. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/index.md +0 -0
  183. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/SECURITY.md +0 -0
  184. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/bots.md +0 -0
  185. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/ci.md +0 -0
  186. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/cli.md +0 -0
  187. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/docs-site.md +0 -0
  188. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/index.md +0 -0
  189. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/ops-tooling.md +0 -0
  190. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/overview.md +0 -0
  191. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/operations/publishing.md +0 -0
  192. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  193. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/rooms.md +0 -0
  194. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  195. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  196. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  197. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  198. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  199. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  200. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  201. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  202. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  203. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  204. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  205. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  206. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  207. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  208. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  209. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  210. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  211. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  212. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  213. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  214. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/01-pair-programming.md +0 -0
  215. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/02-code-review-ensemble.md +0 -0
  216. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/03-cross-server-delegation.md +0 -0
  217. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/04-knowledge-propagation.md +0 -0
  218. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/05-the-observer.md +0 -0
  219. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/06-cross-server-ops.md +0 -0
  220. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/07-supervisor-intervention.md +0 -0
  221. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/08-apps-as-agents.md +0 -0
  222. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/09-research-swarm.md +0 -0
  223. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases/10-agent-lifecycle.md +0 -0
  224. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/use-cases-index.md +0 -0
  225. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/docs/what-is-culture.md +0 -0
  226. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/README.md +0 -0
  227. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/config.py +0 -0
  228. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/daemon.py +0 -0
  229. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/ipc.py +0 -0
  230. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/irc_transport.py +0 -0
  231. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/message_buffer.py +0 -0
  232. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/skill/SKILL.md +0 -0
  233. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/skill/irc_client.py +0 -0
  234. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/socket_server.py +0 -0
  235. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/packages/agent-harness/webhook.py +0 -0
  236. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  237. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  238. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  239. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  240. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/sonar-project.properties +0 -0
  241. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/__init__.py +0 -0
  242. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/conftest.py +0 -0
  243. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_acp_daemon.py +0 -0
  244. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_agent_runner.py +0 -0
  245. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_bot.py +0 -0
  246. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_bot_config.py +0 -0
  247. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_bot_manager.py +0 -0
  248. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_bots_integration.py +0 -0
  249. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_channel.py +0 -0
  250. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_codex_daemon.py +0 -0
  251. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_connection.py +0 -0
  252. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_console_client.py +0 -0
  253. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_console_commands.py +0 -0
  254. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_console_connection.py +0 -0
  255. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_console_icons.py +0 -0
  256. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_console_integration.py +0 -0
  257. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_copilot_daemon.py +0 -0
  258. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_daemon.py +0 -0
  259. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_daemon_config.py +0 -0
  260. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_daemon_ipc.py +0 -0
  261. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_discovery.py +0 -0
  262. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_federation.py +0 -0
  263. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_history.py +0 -0
  264. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_http_listener.py +0 -0
  265. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_integration_layer5.py +0 -0
  266. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_ipc.py +0 -0
  267. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_irc_transport.py +0 -0
  268. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_link_reconnect.py +0 -0
  269. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_mentions.py +0 -0
  270. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_mesh_config.py +0 -0
  271. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_message.py +0 -0
  272. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_message_buffer.py +0 -0
  273. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_messaging.py +0 -0
  274. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_modes.py +0 -0
  275. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_overview_collector.py +0 -0
  276. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_overview_model.py +0 -0
  277. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_overview_renderer.py +0 -0
  278. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_overview_web.py +0 -0
  279. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_persistence.py +0 -0
  280. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_pidfile.py +0 -0
  281. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_room_persistence.py +0 -0
  282. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_rooms.py +0 -0
  283. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_rooms_federation.py +0 -0
  284. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_rooms_integration.py +0 -0
  285. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_server_icon_skill.py +0 -0
  286. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_setup_update_cli.py +0 -0
  287. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_skill_client.py +0 -0
  288. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_skills.py +0 -0
  289. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_socket_server.py +0 -0
  290. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_supervisor.py +0 -0
  291. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_template_engine.py +0 -0
  292. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_thread_buffer.py +0 -0
  293. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_threads.py +0 -0
  294. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_virtual_client.py +0 -0
  295. {agentirc_cli-3.0.0 → agentirc_cli-3.0.2}/tests/test_webhook.py +0 -0
@@ -4,6 +4,21 @@ 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
+ ## [3.0.2] - 2026-04-06
8
+
9
+
10
+ ### Fixed
11
+
12
+ - Server startup readiness — culture server start now waits for port to accept connections before returning
13
+ - Added startup phase logging to server log for diagnosing slow starts
14
+
15
+ ## [3.0.1] - 2026-04-06
16
+
17
+
18
+ ### Fixed
19
+
20
+ - Fix empty error message when running `culture overview` against a starting or unreachable server
21
+
7
22
  ## [3.0.0] - 2026-04-06
8
23
 
9
24
 
@@ -42,7 +42,7 @@ When implementing features, write a corresponding markdown doc in `docs/` descri
42
42
 
43
43
  ## Testing
44
44
 
45
- - `pytest` + `pytest-asyncio`
45
+ - `pytest` + `pytest-asyncio`, always run with `-n auto` for parallel execution
46
46
  - No mocks for the server — tests spin up real server instances on random ports with real TCP connections
47
47
  - Validate each layer with real IRC clients (weechat/irssi)
48
48
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 3.0.0
3
+ Version: 3.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
@@ -29,6 +29,7 @@ import logging
29
29
  import os
30
30
  import shutil
31
31
  import signal
32
+ import socket
32
33
  import subprocess
33
34
  import sys
34
35
  import time
@@ -493,6 +494,39 @@ def _cmd_server(args: argparse.Namespace) -> None:
493
494
  print(f"Default server set to '{args.name}'")
494
495
 
495
496
 
497
+ def _wait_for_port(
498
+ host: str,
499
+ port: int,
500
+ pid: int,
501
+ timeout: float = 30,
502
+ ) -> tuple[bool, str]:
503
+ """Poll *host*:*port* until a TCP connect succeeds or *timeout* expires.
504
+
505
+ Returns ``(True, "")`` on success, or ``(False, reason)`` on failure.
506
+ Checks that *pid* is still alive on every iteration so we fail fast if
507
+ the child crashes (e.g. because the port was already in use).
508
+ """
509
+ check_host = "127.0.0.1" if host == "0.0.0.0" else host
510
+ deadline = time.monotonic() + timeout
511
+ while time.monotonic() < deadline:
512
+ if not is_process_alive(pid):
513
+ return False, "failed to start"
514
+ try:
515
+ s = socket.create_connection((check_host, port), timeout=0.5)
516
+ s.close()
517
+ except OSError:
518
+ time.sleep(0.2)
519
+ continue
520
+ # Port responded — give the child a moment, then confirm it's
521
+ # still alive (guards against connecting to a *stale* listener
522
+ # on the same port while our child crashes).
523
+ time.sleep(0.1)
524
+ if not is_process_alive(pid):
525
+ return False, "failed to start"
526
+ return True, ""
527
+ return False, "started but not yet accepting connections"
528
+
529
+
496
530
  def _server_start(args: argparse.Namespace) -> None:
497
531
  pid_name = f"server-{args.name}"
498
532
 
@@ -532,19 +566,33 @@ def _server_start(args: argparse.Namespace) -> None:
532
566
  # Fork to daemonize
533
567
  pid = os.fork()
534
568
  if pid > 0:
535
- time.sleep(0.2)
536
- if is_process_alive(pid):
537
- print(f"Server '{args.name}' started (PID {pid})")
538
- print(f" Listening on {args.host}:{args.port}")
539
- print(f" Logs: {LOG_DIR}/server-{args.name}.log")
540
- # Auto-set default server if none is set
541
- from culture.pidfile import read_default_server, write_default_server
542
-
543
- if read_default_server() is None:
544
- write_default_server(args.name)
569
+ log_hint = f"{LOG_DIR}/server-{args.name}.log"
570
+
571
+ if args.port == 0:
572
+ # Ephemeral port — can't probe; fall back to process-alive check
573
+ time.sleep(0.5)
574
+ if not is_process_alive(pid):
575
+ print(f"Server '{args.name}' failed to start", file=sys.stderr)
576
+ print(f" Check logs: {log_hint}", file=sys.stderr)
577
+ sys.exit(1)
545
578
  else:
546
- print(f"Server '{args.name}' failed to start", file=sys.stderr)
547
- sys.exit(1)
579
+ ok, err = _wait_for_port(args.host, args.port, pid, timeout=30)
580
+ if not ok:
581
+ print(
582
+ f"Server '{args.name}' {err}",
583
+ file=sys.stderr,
584
+ )
585
+ print(f" Check logs: {log_hint}", file=sys.stderr)
586
+ sys.exit(1)
587
+
588
+ print(f"Server '{args.name}' started (PID {pid})")
589
+ print(f" Listening on {args.host}:{args.port}")
590
+ print(f" Logs: {log_hint}")
591
+ # Auto-set default server if none is set
592
+ from culture.pidfile import read_default_server, write_default_server
593
+
594
+ if read_default_server() is None:
595
+ write_default_server(args.name)
548
596
  return
549
597
 
550
598
  # Child: detach from parent session
@@ -562,6 +610,13 @@ def _server_start(args: argparse.Namespace) -> None:
562
610
  os.dup2(devnull, 0)
563
611
  os.close(devnull)
564
612
 
613
+ # Reconfigure logging so handlers write to the redirected stderr (log file)
614
+ logging.basicConfig(
615
+ level=logging.INFO,
616
+ format="%(asctime)s %(name)s %(levelname)s %(message)s",
617
+ force=True,
618
+ )
619
+
565
620
  write_pid(pid_name, os.getpid())
566
621
 
567
622
  try:
@@ -1593,14 +1648,31 @@ def _cmd_overview(args: argparse.Namespace) -> None:
1593
1648
  )
1594
1649
  return
1595
1650
 
1596
- mesh = asyncio.run(
1597
- collect_mesh_state(
1598
- host=config.server.host,
1599
- port=config.server.port,
1600
- server_name=config.server.name,
1601
- message_limit=message_limit,
1651
+ host, port = config.server.host, config.server.port
1652
+ try:
1653
+ mesh = asyncio.run(
1654
+ collect_mesh_state(
1655
+ host=host,
1656
+ port=port,
1657
+ server_name=config.server.name,
1658
+ message_limit=message_limit,
1659
+ )
1602
1660
  )
1603
- )
1661
+ except ConnectionRefusedError:
1662
+ print(
1663
+ f"Error: could not connect to {host}:{port} — is the server running?",
1664
+ file=sys.stderr,
1665
+ )
1666
+ sys.exit(1)
1667
+ except TimeoutError:
1668
+ print(
1669
+ f"Error: server at {host}:{port} not responding" " — it may still be starting up",
1670
+ file=sys.stderr,
1671
+ )
1672
+ sys.exit(1)
1673
+ except OSError as exc:
1674
+ print(f"Error: {exc}", file=sys.stderr)
1675
+ sys.exit(1)
1604
1676
  output = render_text(
1605
1677
  mesh,
1606
1678
  room_filter=args.room,
@@ -116,28 +116,35 @@ async def _connect(
116
116
  timeout=REGISTER_TIMEOUT,
117
117
  )
118
118
  nick = _temp_nick(server_name)
119
- writer.write(f"NICK {nick}\r\nUSER overview 0 * :overview\r\n".encode())
120
- await writer.drain()
119
+ try:
120
+ writer.write(f"NICK {nick}\r\nUSER overview 0 * :overview\r\n".encode())
121
+ await writer.drain()
121
122
 
122
- deadline = asyncio.get_event_loop().time() + REGISTER_TIMEOUT
123
- while True:
124
- remaining = deadline - asyncio.get_event_loop().time()
125
- if remaining <= 0:
126
- raise TimeoutError("Registration timed out")
127
- data = await asyncio.wait_for(reader.readline(), timeout=remaining)
128
- line = data.decode().strip()
129
- if not line:
130
- continue
131
- msg = IRCMessage.parse(line)
132
- if msg.command == "PING":
133
- writer.write(f"PONG :{msg.params[0]}\r\n".encode())
134
- await writer.drain()
135
- elif msg.command == "001":
136
- return reader, writer, nick
137
- elif msg.command == "433":
138
- nick = _temp_nick(server_name)
139
- writer.write(f"NICK {nick}\r\n".encode())
140
- await writer.drain()
123
+ deadline = asyncio.get_event_loop().time() + REGISTER_TIMEOUT
124
+ while True:
125
+ remaining = deadline - asyncio.get_event_loop().time()
126
+ if remaining <= 0:
127
+ raise TimeoutError("Registration timed out")
128
+ try:
129
+ data = await asyncio.wait_for(reader.readline(), timeout=remaining)
130
+ except asyncio.TimeoutError:
131
+ raise TimeoutError("Registration timed out") from None
132
+ line = data.decode().strip()
133
+ if not line:
134
+ continue
135
+ msg = IRCMessage.parse(line)
136
+ if msg.command == "PING":
137
+ writer.write(f"PONG :{msg.params[0]}\r\n".encode())
138
+ await writer.drain()
139
+ elif msg.command == "001":
140
+ return reader, writer, nick
141
+ elif msg.command == "433":
142
+ nick = _temp_nick(server_name)
143
+ writer.write(f"NICK {nick}\r\n".encode())
144
+ await writer.drain()
145
+ except BaseException:
146
+ await _disconnect(writer)
147
+ raise
141
148
 
142
149
 
143
150
  async def _disconnect(writer: asyncio.StreamWriter) -> None:
@@ -10,6 +10,8 @@ from culture.server.channel import Channel
10
10
  from culture.server.config import ServerConfig
11
11
  from culture.server.skill import Event, Skill
12
12
 
13
+ logger = logging.getLogger(__name__)
14
+
13
15
  if TYPE_CHECKING:
14
16
  from culture.bots.virtual_client import VirtualClient
15
17
  from culture.server.client import Client
@@ -41,16 +43,25 @@ class IRCd:
41
43
  self.bot_manager = None # set in start() if webhook_port configured
42
44
 
43
45
  async def start(self) -> None:
46
+ logger.info("Registering default skills...")
44
47
  await self._register_default_skills()
48
+
49
+ logger.info("Restoring persistent rooms...")
45
50
  self._restore_persistent_rooms()
46
51
 
47
52
  # Initialize bot manager and webhook HTTP listener
48
53
  from culture.bots.bot_manager import BotManager
49
54
  from culture.bots.http_listener import HttpListener
50
55
 
56
+ logger.info("Loading bots...")
51
57
  self.bot_manager = BotManager(self)
52
58
  await self.bot_manager.load_bots()
53
59
 
60
+ logger.info(
61
+ "Binding IRC socket on %s:%d...",
62
+ self.config.host,
63
+ self.config.port,
64
+ )
54
65
  self._server = await asyncio.start_server(
55
66
  self._handle_connection,
56
67
  self.config.host,
@@ -63,16 +74,22 @@ class IRCd:
63
74
  self.config.webhook_port,
64
75
  )
65
76
  try:
77
+ logger.info(
78
+ "Starting webhook listener on port %d...",
79
+ self.config.webhook_port,
80
+ )
66
81
  await self._http_listener.start()
67
82
  except OSError:
68
83
  # Port unavailable (e.g. in tests using port 0 that got
69
84
  # assigned an in-use ephemeral port). Non-fatal — bots
70
85
  # still work, just without the HTTP endpoint.
71
- logging.getLogger(__name__).warning(
86
+ logger.warning(
72
87
  "Could not start webhook listener on port %d",
73
88
  self.config.webhook_port,
74
89
  )
75
90
 
91
+ logger.info("Server ready")
92
+
76
93
  async def _register_default_skills(self) -> None:
77
94
  from culture.server.skills.history import HistorySkill
78
95
  from culture.server.skills.icon import IconSkill
@@ -101,9 +118,7 @@ class IRCd:
101
118
  try:
102
119
  await skill.on_event(event)
103
120
  except Exception:
104
- logging.getLogger(__name__).exception(
105
- "Skill %s failed on event %s", skill.name, event.type
106
- )
121
+ logger.exception("Skill %s failed on event %s", skill.name, event.type)
107
122
 
108
123
  # Relay to linked peers — only relay locally-originated events
109
124
  # (no mesh routing; scope is direct peers only)
@@ -112,7 +127,7 @@ class IRCd:
112
127
  try:
113
128
  await link.relay_event(event)
114
129
  except Exception:
115
- logging.getLogger(__name__).exception("Failed to relay event to %s", peer_name)
130
+ logger.exception("Failed to relay event to %s", peer_name)
116
131
 
117
132
  def get_skill_for_command(self, command: str) -> Skill | None:
118
133
  for skill in self.skills:
@@ -190,7 +205,6 @@ class IRCd:
190
205
 
191
206
  async def _retry_link_loop(self, peer_name: str, link_config, state: dict) -> None:
192
207
  """Retry connecting to a peer with exponential backoff."""
193
- logger = logging.getLogger(__name__)
194
208
  try:
195
209
  while True:
196
210
  await asyncio.sleep(state["delay"])
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentirc-cli"
3
- version = "3.0.0"
3
+ version = "3.0.2"
4
4
  description = "Legacy alias for culture — install culture instead"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1,133 @@
1
+ """Tests for overview CLI subcommand argument parsing and error handling."""
2
+
3
+ import subprocess
4
+ import sys
5
+ from unittest.mock import patch
6
+
7
+ import pytest
8
+
9
+
10
+ def test_overview_help():
11
+ """The overview subcommand is registered and has help."""
12
+ result = subprocess.run(
13
+ [sys.executable, "-m", "culture", "overview", "--help"],
14
+ capture_output=True,
15
+ text=True,
16
+ )
17
+ assert result.returncode == 0
18
+ assert "--room" in result.stdout
19
+ assert "--agent" in result.stdout
20
+ assert "--messages" in result.stdout
21
+ assert "--serve" in result.stdout
22
+ assert "--refresh" in result.stdout
23
+
24
+
25
+ def test_overview_default_args():
26
+ """Default args parse correctly."""
27
+ from culture.cli import _build_parser
28
+
29
+ parser = _build_parser()
30
+ args = parser.parse_args(["overview"])
31
+ assert args.command == "overview"
32
+ assert args.room is None
33
+ assert args.agent is None
34
+ assert args.messages == 4
35
+ assert args.serve is False
36
+ assert args.refresh == 5
37
+
38
+
39
+ def test_overview_with_flags():
40
+ from culture.cli import _build_parser
41
+
42
+ parser = _build_parser()
43
+ args = parser.parse_args(["overview", "--room", "#general", "--messages", "10"])
44
+ assert args.room == "#general"
45
+ assert args.messages == 10
46
+
47
+
48
+ def test_overview_connection_refused(capsys):
49
+ """ConnectionRefusedError produces a helpful message."""
50
+ from culture.cli import _build_parser, _cmd_overview
51
+
52
+ parser = _build_parser()
53
+ args = parser.parse_args(["overview"])
54
+
55
+ with patch(
56
+ "culture.overview.collector.collect_mesh_state",
57
+ side_effect=ConnectionRefusedError(111, "Connection refused"),
58
+ ):
59
+ with pytest.raises(SystemExit, match="1"):
60
+ _cmd_overview(args)
61
+
62
+ captured = capsys.readouterr()
63
+ assert "is the server running?" in captured.err
64
+
65
+
66
+ def test_overview_timeout(capsys):
67
+ """TimeoutError produces a helpful message."""
68
+ from culture.cli import _build_parser, _cmd_overview
69
+
70
+ parser = _build_parser()
71
+ args = parser.parse_args(["overview"])
72
+
73
+ with patch(
74
+ "culture.overview.collector.collect_mesh_state",
75
+ side_effect=TimeoutError("Registration timed out"),
76
+ ):
77
+ with pytest.raises(SystemExit, match="1"):
78
+ _cmd_overview(args)
79
+
80
+ captured = capsys.readouterr()
81
+ assert "not responding" in captured.err
82
+ assert "still be starting up" in captured.err
83
+
84
+
85
+ def test_overview_os_error(capsys):
86
+ """OSError shows the original error details."""
87
+ from culture.cli import _build_parser, _cmd_overview
88
+
89
+ parser = _build_parser()
90
+ args = parser.parse_args(["overview"])
91
+
92
+ with patch(
93
+ "culture.overview.collector.collect_mesh_state",
94
+ side_effect=OSError("Name or service not known"),
95
+ ):
96
+ with pytest.raises(SystemExit, match="1"):
97
+ _cmd_overview(args)
98
+
99
+ captured = capsys.readouterr()
100
+ assert "Name or service not known" in captured.err
101
+
102
+
103
+ @pytest.mark.asyncio
104
+ async def test_connect_timeout_has_message():
105
+ """_connect raises TimeoutError with a non-empty message on registration timeout."""
106
+ import asyncio
107
+
108
+ from culture.overview.collector import _connect
109
+
110
+ # TCP server that accepts but never sends IRC 001 (silent handshake)
111
+ stop = asyncio.Event()
112
+
113
+ async def hold_open(reader, writer):
114
+ await stop.wait()
115
+ writer.close()
116
+
117
+ server = await asyncio.start_server(hold_open, "127.0.0.1", 0)
118
+ port = server.sockets[0].getsockname()[1]
119
+
120
+ try:
121
+ with pytest.raises(TimeoutError, match="Registration timed out"):
122
+ import culture.overview.collector as col
123
+
124
+ original = col.REGISTER_TIMEOUT
125
+ col.REGISTER_TIMEOUT = 0.5
126
+ try:
127
+ await _connect("127.0.0.1", port, "test")
128
+ finally:
129
+ col.REGISTER_TIMEOUT = original
130
+ finally:
131
+ stop.set()
132
+ server.close()
133
+ await server.wait_closed()
@@ -0,0 +1,89 @@
1
+ """Tests for the _wait_for_port readiness helper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import socket
6
+ import threading
7
+ import time
8
+
9
+ from culture.cli import _wait_for_port
10
+
11
+
12
+ def _start_listener(port_holder: list, delay: float = 0) -> socket.socket:
13
+ """Start a TCP listener, optionally after *delay* seconds.
14
+
15
+ Stores the assigned port in *port_holder[0]*.
16
+ """
17
+ srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
18
+ srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
19
+
20
+ def _bind():
21
+ if delay:
22
+ time.sleep(delay)
23
+ srv.bind(("127.0.0.1", 0))
24
+ srv.listen(1)
25
+ port_holder.append(srv.getsockname()[1])
26
+
27
+ t = threading.Thread(target=_bind, daemon=True)
28
+ t.start()
29
+ t.join(timeout=delay + 2)
30
+ return srv
31
+
32
+
33
+ def test_success_immediate():
34
+ """Port already open — should return immediately."""
35
+ port_holder: list[int] = []
36
+ srv = _start_listener(port_holder)
37
+ try:
38
+ ok, err = _wait_for_port(
39
+ "127.0.0.1",
40
+ port_holder[0],
41
+ # Use our own PID (always alive)
42
+ pid=threading.current_thread().native_id or 1,
43
+ timeout=5,
44
+ )
45
+ assert ok
46
+ assert err == ""
47
+ finally:
48
+ srv.close()
49
+
50
+
51
+ def test_timeout_no_listener():
52
+ """No listener — should time out."""
53
+ # Bind then close to get a free port that nothing listens on
54
+ tmp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
55
+ tmp.bind(("127.0.0.1", 0))
56
+ port = tmp.getsockname()[1]
57
+ tmp.close()
58
+
59
+ import os
60
+
61
+ ok, err = _wait_for_port("127.0.0.1", port, pid=os.getpid(), timeout=1)
62
+ assert not ok
63
+ assert "not yet accepting connections" in err
64
+
65
+
66
+ def test_process_dies():
67
+ """Dead PID — should fail fast."""
68
+ import subprocess
69
+
70
+ # Spawn a process that exits immediately to get a dead PID
71
+ p = subprocess.Popen(["true"])
72
+ p.wait()
73
+
74
+ ok, err = _wait_for_port("127.0.0.1", 1, pid=p.pid, timeout=5)
75
+ assert not ok
76
+ assert "failed to start" in err
77
+
78
+
79
+ def test_host_0000_uses_localhost():
80
+ """0.0.0.0 host should probe 127.0.0.1."""
81
+ port_holder: list[int] = []
82
+ srv = _start_listener(port_holder)
83
+ try:
84
+ import os
85
+
86
+ ok, _ = _wait_for_port("0.0.0.0", port_holder[0], pid=os.getpid(), timeout=5)
87
+ assert ok
88
+ finally:
89
+ srv.close()
@@ -561,7 +561,7 @@ wheels = [
561
561
 
562
562
  [[package]]
563
563
  name = "culture"
564
- version = "3.0.0"
564
+ version = "3.0.2"
565
565
  source = { editable = "." }
566
566
  dependencies = [
567
567
  { name = "aiohttp" },
@@ -1,42 +0,0 @@
1
- """Tests for overview CLI subcommand argument parsing."""
2
-
3
- import subprocess
4
- import sys
5
-
6
-
7
- def test_overview_help():
8
- """The overview subcommand is registered and has help."""
9
- result = subprocess.run(
10
- [sys.executable, "-m", "culture", "overview", "--help"],
11
- capture_output=True,
12
- text=True,
13
- )
14
- assert result.returncode == 0
15
- assert "--room" in result.stdout
16
- assert "--agent" in result.stdout
17
- assert "--messages" in result.stdout
18
- assert "--serve" in result.stdout
19
- assert "--refresh" in result.stdout
20
-
21
-
22
- def test_overview_default_args():
23
- """Default args parse correctly."""
24
- from culture.cli import _build_parser
25
-
26
- parser = _build_parser()
27
- args = parser.parse_args(["overview"])
28
- assert args.command == "overview"
29
- assert args.room is None
30
- assert args.agent is None
31
- assert args.messages == 4
32
- assert args.serve is False
33
- assert args.refresh == 5
34
-
35
-
36
- def test_overview_with_flags():
37
- from culture.cli import _build_parser
38
-
39
- parser = _build_parser()
40
- args = parser.parse_args(["overview", "--room", "#general", "--messages", "10"])
41
- assert args.room == "#general"
42
- assert args.messages == 10
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes