agentirc-cli 6.1.1__tar.gz → 6.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/CHANGELOG.md +30 -0
  2. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/PKG-INFO +2 -2
  3. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/README.md +1 -1
  4. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/channel.py +45 -2
  5. agentirc_cli-6.2.1/culture/cli/shared/constants.py +43 -0
  6. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/shared/ipc.py +3 -1
  7. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/console/app.py +170 -34
  8. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/console/commands.py +2 -0
  9. agentirc_cli-6.2.1/culture/console/status.py +74 -0
  10. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/console/widgets/chat.py +24 -1
  11. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/console/widgets/sidebar.py +15 -1
  12. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/observer.py +16 -3
  13. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/cli/index.md +17 -0
  14. agentirc_cli-6.2.1/docs/superpowers/plans/2026-04-12-console-enhancements.md +732 -0
  15. agentirc_cli-6.2.1/docs/superpowers/specs/2026-04-12-console-enhancements-design.md +203 -0
  16. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/pyproject.toml +1 -1
  17. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_console_commands.py +6 -0
  18. agentirc_cli-6.2.1/tests/test_console_fixes_224_227.py +199 -0
  19. agentirc_cli-6.2.1/tests/test_console_status.py +53 -0
  20. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/uv.lock +1 -1
  21. agentirc_cli-6.1.1/culture/cli/shared/constants.py +0 -20
  22. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.claude/skills/pr-review/SKILL.md +0 -0
  23. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.claude/skills/run-tests/SKILL.md +0 -0
  24. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
  25. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.flake8 +0 -0
  26. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.github/workflows/docs-check.yml +0 -0
  27. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.github/workflows/publish.yml +0 -0
  28. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.github/workflows/security-checks.yml +0 -0
  29. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.github/workflows/tests.yml +0 -0
  30. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.gitignore +0 -0
  31. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.markdownlint-cli2.yaml +0 -0
  32. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.pr_agent.toml +0 -0
  33. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.pre-commit-config.yaml +0 -0
  34. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/.pylintrc +0 -0
  35. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/CLAUDE.md +0 -0
  36. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/Gemfile +0 -0
  37. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/Gemfile.lock +0 -0
  38. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/LICENSE +0 -0
  39. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/SECURITY.md +0 -0
  40. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_config.agentirc.yml +0 -0
  41. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_config.base.yml +0 -0
  42. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_config.culture.yml +0 -0
  43. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_data/sites.yml +0 -0
  44. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_includes/head_custom.html +0 -0
  45. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_plugins/site_filter.rb +0 -0
  46. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_sass/color_schemes/anthropic.scss +0 -0
  47. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_sass/color_schemes/dark-terminal.scss +0 -0
  48. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/_sass/custom/custom.scss +0 -0
  49. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/assets/images/IMG_3183.png +0 -0
  50. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/assets/images/apple-touch-icon.png +0 -0
  51. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/assets/images/favicon-16x16.png +0 -0
  52. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/assets/images/favicon-32x32.png +0 -0
  53. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/assets/images/favicon.ico +0 -0
  54. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/assets/images/og-agentirc.png +0 -0
  55. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/assets/images/og-culture.png +0 -0
  56. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/__init__.py +0 -0
  57. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/__main__.py +0 -0
  58. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/CLAUDE.md +0 -0
  59. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/__init__.py +0 -0
  60. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/__main__.py +0 -0
  61. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/channel.py +0 -0
  62. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/client.py +0 -0
  63. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/config.py +0 -0
  64. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/docs/agentirc-architecture.md +0 -0
  65. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/docs/agentirc-features.md +0 -0
  66. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/docs/agentirc-skill.md +0 -0
  67. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/docs/agentirc.md +0 -0
  68. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/history_store.py +0 -0
  69. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/ircd.py +0 -0
  70. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/remote_client.py +0 -0
  71. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/room_store.py +0 -0
  72. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/rooms_util.py +0 -0
  73. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/server_link.py +0 -0
  74. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/skill.py +0 -0
  75. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/skills/__init__.py +0 -0
  76. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/skills/history.py +0 -0
  77. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/skills/icon.py +0 -0
  78. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/skills/rooms.py +0 -0
  79. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/skills/threads.py +0 -0
  80. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/agentirc/thread_store.py +0 -0
  81. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/aio.py +0 -0
  82. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/bots/__init__.py +0 -0
  83. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/bots/bot.py +0 -0
  84. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/bots/bot_manager.py +0 -0
  85. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/bots/config.py +0 -0
  86. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/bots/http_listener.py +0 -0
  87. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/bots/template_engine.py +0 -0
  88. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/bots/virtual_client.py +0 -0
  89. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/__init__.py +0 -0
  90. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/agent.py +0 -0
  91. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/bot.py +0 -0
  92. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/mesh.py +0 -0
  93. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/server.py +0 -0
  94. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/shared/__init__.py +0 -0
  95. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/shared/display.py +0 -0
  96. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/shared/formatting.py +0 -0
  97. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/shared/mesh.py +0 -0
  98. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/shared/process.py +0 -0
  99. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/cli/skills.py +0 -0
  100. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/__init__.py +0 -0
  101. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/__init__.py +0 -0
  102. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/agent_runner.py +0 -0
  103. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/config.py +0 -0
  104. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/culture.yaml +0 -0
  105. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/daemon.py +0 -0
  106. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/ipc.py +0 -0
  107. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/irc_transport.py +0 -0
  108. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/message_buffer.py +0 -0
  109. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/skill/SKILL.md +0 -0
  110. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/skill/__init__.py +0 -0
  111. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/skill/irc_client.py +0 -0
  112. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/socket_server.py +0 -0
  113. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/supervisor.py +0 -0
  114. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/acp/webhook.py +0 -0
  115. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/__init__.py +0 -0
  116. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/__main__.py +0 -0
  117. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/agent_runner.py +0 -0
  118. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/config.py +0 -0
  119. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/culture.yaml +0 -0
  120. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/daemon.py +0 -0
  121. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/ipc.py +0 -0
  122. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/irc_transport.py +0 -0
  123. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/message_buffer.py +0 -0
  124. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/skill/SKILL.md +0 -0
  125. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/skill/__init__.py +0 -0
  126. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/skill/irc_client.py +0 -0
  127. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/socket_server.py +0 -0
  128. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/supervisor.py +0 -0
  129. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/claude/webhook.py +0 -0
  130. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/__init__.py +0 -0
  131. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/agent_runner.py +0 -0
  132. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/config.py +0 -0
  133. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/culture.yaml +0 -0
  134. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/daemon.py +0 -0
  135. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/ipc.py +0 -0
  136. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/irc_transport.py +0 -0
  137. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/message_buffer.py +0 -0
  138. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/skill/SKILL.md +0 -0
  139. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/skill/__init__.py +0 -0
  140. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/skill/irc_client.py +0 -0
  141. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/socket_server.py +0 -0
  142. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/supervisor.py +0 -0
  143. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/codex/webhook.py +0 -0
  144. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/__init__.py +0 -0
  145. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/agent_runner.py +0 -0
  146. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/config.py +0 -0
  147. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/culture.yaml +0 -0
  148. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/daemon.py +0 -0
  149. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/ipc.py +0 -0
  150. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/irc_transport.py +0 -0
  151. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/message_buffer.py +0 -0
  152. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/skill/SKILL.md +0 -0
  153. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/skill/__init__.py +0 -0
  154. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/skill/irc_client.py +0 -0
  155. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/socket_server.py +0 -0
  156. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/supervisor.py +0 -0
  157. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/clients/copilot/webhook.py +0 -0
  158. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/config.py +0 -0
  159. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/console/__init__.py +0 -0
  160. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/console/client.py +0 -0
  161. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/console/widgets/__init__.py +0 -0
  162. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/console/widgets/info_panel.py +0 -0
  163. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/credentials.py +0 -0
  164. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/formatting.py +0 -0
  165. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/learn_prompt.py +0 -0
  166. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/mesh_config.py +0 -0
  167. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/overview/__init__.py +0 -0
  168. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/overview/collector.py +0 -0
  169. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/overview/model.py +0 -0
  170. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/overview/renderer_text.py +0 -0
  171. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/overview/renderer_web.py +0 -0
  172. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/overview/web/style.css +0 -0
  173. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/persistence.py +0 -0
  174. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/pidfile.py +0 -0
  175. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/__init__.py +0 -0
  176. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/commands.py +0 -0
  177. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/extensions/federation.md +0 -0
  178. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/extensions/history.md +0 -0
  179. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/extensions/icons.md +0 -0
  180. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/extensions/rooms.md +0 -0
  181. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/extensions/tags.md +0 -0
  182. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/extensions/threads.md +0 -0
  183. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/message.py +0 -0
  184. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/protocol-index.md +0 -0
  185. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/protocol/replies.py +0 -0
  186. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/culture/skills/culture/SKILL.md +0 -0
  187. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/README.md +0 -0
  188. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/agentirc/architecture-overview.md +0 -0
  189. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/agentirc/index.md +0 -0
  190. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/agentirc/why-agentirc.md +0 -0
  191. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/agent-lifecycle.md +0 -0
  192. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/choose-a-harness.md +0 -0
  193. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/index.md +0 -0
  194. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/mental-model.md +0 -0
  195. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/operate.md +0 -0
  196. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/patterns.md +0 -0
  197. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/quickstart.md +0 -0
  198. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/reflective-development.md +0 -0
  199. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/vision-patterns-index.md +0 -0
  200. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/vision.md +0 -0
  201. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/culture/why-culture.md +0 -0
  202. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/architecture/agent-harness-spec.md +0 -0
  203. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/architecture/index.md +0 -0
  204. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/architecture/layers.md +0 -0
  205. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/architecture/threads.md +0 -0
  206. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/cli/commands.md +0 -0
  207. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/harnesses/acp.md +0 -0
  208. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/harnesses/claude.md +0 -0
  209. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/harnesses/codex.md +0 -0
  210. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/harnesses/copilot.md +0 -0
  211. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/harnesses/index.md +0 -0
  212. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/index.md +0 -0
  213. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/server/architecture.md +0 -0
  214. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/server/config.md +0 -0
  215. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/server/deployment.md +0 -0
  216. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/server/index.md +0 -0
  217. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/reference/server/security.md +0 -0
  218. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  219. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/concepts/federation.md +0 -0
  220. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/concepts/harnesses.md +0 -0
  221. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/concepts/humans-and-agents.md +0 -0
  222. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/concepts/index.md +0 -0
  223. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/concepts/persistence.md +0 -0
  224. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/concepts/rooms.md +0 -0
  225. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/demos/magic-demo.md +0 -0
  226. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/guides/first-session.md +0 -0
  227. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/guides/index.md +0 -0
  228. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/guides/join-as-human.md +0 -0
  229. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/guides/local-setup.md +0 -0
  230. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/guides/multi-machine.md +0 -0
  231. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/01-pair-programming.md +0 -0
  232. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/02-code-review-ensemble.md +0 -0
  233. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/03-cross-server-delegation.md +0 -0
  234. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/04-knowledge-propagation.md +0 -0
  235. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/05-the-observer.md +0 -0
  236. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/06-cross-server-ops.md +0 -0
  237. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/07-supervisor-intervention.md +0 -0
  238. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/08-apps-as-agents.md +0 -0
  239. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/09-research-swarm.md +0 -0
  240. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases/10-agent-lifecycle.md +0 -0
  241. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/shared/use-cases-index.md +0 -0
  242. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  243. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  244. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  245. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  246. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  247. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  248. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  249. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  250. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  251. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
  252. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  253. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  254. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  255. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  256. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  257. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  258. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  259. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  260. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  261. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  262. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  263. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  264. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
  265. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
  266. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
  267. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
  268. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/favicon.ico +0 -0
  269. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/README.md +0 -0
  270. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/config.py +0 -0
  271. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/culture.yaml +0 -0
  272. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/daemon.py +0 -0
  273. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/ipc.py +0 -0
  274. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/irc_transport.py +0 -0
  275. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/message_buffer.py +0 -0
  276. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/skill/SKILL.md +0 -0
  277. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/skill/irc_client.py +0 -0
  278. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/socket_server.py +0 -0
  279. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/packages/agent-harness/webhook.py +0 -0
  280. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  281. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  282. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  283. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  284. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/robots.txt +0 -0
  285. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/sonar-project.properties +0 -0
  286. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/__init__.py +0 -0
  287. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/conftest.py +0 -0
  288. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_acp_daemon.py +0 -0
  289. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_agent_runner.py +0 -0
  290. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_archive.py +0 -0
  291. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_bot.py +0 -0
  292. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_bot_config.py +0 -0
  293. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_bot_manager.py +0 -0
  294. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_bots_integration.py +0 -0
  295. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_channel.py +0 -0
  296. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_channel_cli.py +0 -0
  297. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_codex_daemon.py +0 -0
  298. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_connection.py +0 -0
  299. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_console_client.py +0 -0
  300. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_console_connection.py +0 -0
  301. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_console_icons.py +0 -0
  302. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_console_integration.py +0 -0
  303. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_copilot_daemon.py +0 -0
  304. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_credentials.py +0 -0
  305. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_culture_config.py +0 -0
  306. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_daemon.py +0 -0
  307. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_daemon_config.py +0 -0
  308. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_daemon_ipc.py +0 -0
  309. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_discovery.py +0 -0
  310. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_display.py +0 -0
  311. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_federation.py +0 -0
  312. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_history.py +0 -0
  313. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_http_listener.py +0 -0
  314. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_integration_layer5.py +0 -0
  315. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_ipc.py +0 -0
  316. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_irc_transport.py +0 -0
  317. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_learn_prompt.py +0 -0
  318. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_link_reconnect.py +0 -0
  319. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_manifest_config.py +0 -0
  320. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_mention_alias.py +0 -0
  321. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_mention_target_cleanup.py +0 -0
  322. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_mention_warning.py +0 -0
  323. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_mentions.py +0 -0
  324. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_mesh_config.py +0 -0
  325. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_mesh_readiness.py +0 -0
  326. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_message.py +0 -0
  327. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_message_buffer.py +0 -0
  328. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_messaging.py +0 -0
  329. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_migrate_cli.py +0 -0
  330. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_modes.py +0 -0
  331. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_overview_cli.py +0 -0
  332. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_overview_collector.py +0 -0
  333. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_overview_model.py +0 -0
  334. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_overview_renderer.py +0 -0
  335. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_overview_web.py +0 -0
  336. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_persistence.py +0 -0
  337. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_pidfile.py +0 -0
  338. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_poll_loop.py +0 -0
  339. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_register_cli.py +0 -0
  340. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_room_persistence.py +0 -0
  341. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_rooms.py +0 -0
  342. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_rooms_federation.py +0 -0
  343. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_rooms_integration.py +0 -0
  344. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_server_icon_skill.py +0 -0
  345. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_setup_update_cli.py +0 -0
  346. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_skill_client.py +0 -0
  347. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_skill_docs.py +0 -0
  348. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_skills.py +0 -0
  349. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_socket_server.py +0 -0
  350. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_supervisor.py +0 -0
  351. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_template_engine.py +0 -0
  352. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_thread_buffer.py +0 -0
  353. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_threads.py +0 -0
  354. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_virtual_client.py +0 -0
  355. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_wait_for_port.py +0 -0
  356. {agentirc_cli-6.1.1 → agentirc_cli-6.2.1}/tests/test_webhook.py +0 -0
@@ -4,6 +4,36 @@ 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
+ ## [6.2.1] - 2026-04-13
8
+
9
+
10
+ ### Added
11
+
12
+ - Copy-paste guidance in help screen (Shift+drag bypasses TUI mouse capture in modern terminals)
13
+
14
+
15
+ ### Fixed
16
+
17
+ - #227: Tab now cycles channels (added priority=True to override Textual Screen focus-cycling)
18
+ - #226: Alt+Left/Right jump by word in chat input; Alt+Backspace deletes previous word
19
+ - #225: `culture channel message` interprets literal \n, \t, and \\ (escape-an-escape); observer splits multi-line text into one PRIVMSG per line and rejects all-empty-after-interpretation input with a non-zero exit
20
+ - #224: Exiting overview now reloads the current channel history (was empty)
21
+ - Help screen now opens on F1 (Ctrl+H stays as secondary — most terminals forward it as Backspace)
22
+
23
+ ## [6.2.0] - 2026-04-12
24
+
25
+
26
+ ### Added
27
+
28
+ - Agent status indicators in console sidebar (#218) — shows working/idle/paused/circuit-open for each agent
29
+ - Auto-read channel history on switch (#219) — loads last 20 messages when switching channels via Tab, sidebar click, or /join
30
+ - Help menu — /help command and Ctrl+H keybinding showing all commands and keybindings
31
+
32
+
33
+ ### Fixed
34
+
35
+ - Joined message no longer wiped by channel switch clear_log
36
+
7
37
  ## [6.1.1] - 2026-04-11
8
38
 
9
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 6.1.1
3
+ Version: 6.2.1
4
4
  Summary: Legacy alias for culture — install culture instead
5
5
  Project-URL: Homepage, https://github.com/OriNachum/culture
6
6
  Author: Ori Nachum
@@ -27,7 +27,7 @@ Description-Content-Type: text/markdown
27
27
  The complete human-agent collaboration system built around AgentIRC.
28
28
 
29
29
  **AgentIRC** is the IRC-native runtime for persistent AI agents and humans in shared live rooms.
30
- **Culture** is the full solution — CLI, harnesses, workflows, and multi-machine mesh.
30
+ **Culture** is the full solution — CLI, harnesses, console, workflows, and multi-machine mesh.
31
31
 
32
32
  ## Start here
33
33
 
@@ -3,7 +3,7 @@
3
3
  The complete human-agent collaboration system built around AgentIRC.
4
4
 
5
5
  **AgentIRC** is the IRC-native runtime for persistent AI agents and humans in shared live rooms.
6
- **Culture** is the full solution — CLI, harnesses, workflows, and multi-machine mesh.
6
+ **Culture** is the full solution — CLI, harnesses, console, workflows, and multi-machine mesh.
7
7
 
8
8
  ## Start here
9
9
 
@@ -208,6 +208,40 @@ def _cmd_read(args: argparse.Namespace) -> None:
208
208
  print(msg)
209
209
 
210
210
 
211
+ def _interpret_escapes(text: str) -> str:
212
+ """Convert shell-literal ``\\n`` / ``\\t`` / ``\\\\`` sequences to real chars.
213
+
214
+ Walks the string left-to-right so a preceding backslash escapes the next
215
+ character — ``\\\\n`` stays as the two chars ``\\`` + ``n``, while ``\\n``
216
+ becomes a real newline. Supported escapes: ``\\n`` → newline, ``\\t`` →
217
+ tab, ``\\\\`` → single backslash. Any other ``\\x`` pair is passed through
218
+ unchanged so we don't surprise users with ``\\x..`` / ``\\u....`` style
219
+ interpretation that ``codecs.decode(..., "unicode_escape")`` would do.
220
+ """
221
+ out: list[str] = []
222
+ i = 0
223
+ n = len(text)
224
+ while i < n:
225
+ ch = text[i]
226
+ if ch == "\\" and i + 1 < n:
227
+ nxt = text[i + 1]
228
+ if nxt == "n":
229
+ out.append("\n")
230
+ i += 2
231
+ continue
232
+ if nxt == "t":
233
+ out.append("\t")
234
+ i += 2
235
+ continue
236
+ if nxt == "\\":
237
+ out.append("\\")
238
+ i += 2
239
+ continue
240
+ out.append(ch)
241
+ i += 1
242
+ return "".join(out)
243
+
244
+
211
245
  def _cmd_message(args: argparse.Namespace) -> None:
212
246
  if not args.target.strip():
213
247
  print("Error: channel name cannot be empty", file=sys.stderr)
@@ -216,14 +250,23 @@ def _cmd_message(args: argparse.Namespace) -> None:
216
250
  print("Error: message text cannot be empty", file=sys.stderr)
217
251
  sys.exit(1)
218
252
  target = args.target if args.target.startswith("#") else f"#{args.target}"
253
+ text = _interpret_escapes(args.text)
254
+
255
+ # After escape interpretation, reject input that has no non-empty line —
256
+ # otherwise we'd print "Sent to ..." while nothing actually goes out.
257
+ if not any(line.strip() for line in text.split("\n")):
258
+ print(
259
+ "Error: message text has no non-empty line after escape interpretation", file=sys.stderr
260
+ )
261
+ sys.exit(1)
219
262
 
220
- resp = _try_ipc("irc_send", channel=target, message=args.text)
263
+ resp = _try_ipc("irc_send", channel=target, message=text)
221
264
  if resp and resp.get("ok"):
222
265
  print(f"Sent to {target}")
223
266
  return
224
267
 
225
268
  observer = get_observer(args.config)
226
- asyncio.run(observer.send_message(target, args.text))
269
+ asyncio.run(observer.send_message(target, text))
227
270
  print(f"Sent to {target}")
228
271
 
229
272
 
@@ -0,0 +1,43 @@
1
+ """Shared constants for culture CLI modules."""
2
+
3
+ import os
4
+ import stat
5
+
6
+ from culture.bots.config import BOT_CONFIG_FILE # noqa: F401
7
+
8
+ DEFAULT_CONFIG = os.path.expanduser("~/.culture/server.yaml")
9
+ LOG_DIR = os.path.expanduser("~/.culture/logs")
10
+
11
+ _CONFIG_HELP = "Config file path"
12
+ _SERVER_NAME_HELP = "Server name"
13
+ _BOT_NAME_HELP = "Bot name"
14
+
15
+ DEFAULT_CHANNEL = "#general"
16
+ NO_AGENTS_MSG = "No agents configured"
17
+ CULTURE_DIR = ".culture"
18
+ AGENTS_YAML = "agents.yaml"
19
+
20
+ DEFAULT_SERVER_CONFIG = os.path.expanduser("~/.culture/server.yaml")
21
+ LEGACY_CONFIG = os.path.expanduser("~/.culture/agents.yaml")
22
+
23
+
24
+ def culture_runtime_dir() -> str:
25
+ """Return a safe directory for culture daemon sockets.
26
+
27
+ Uses ``$XDG_RUNTIME_DIR`` when available (user-private, set by
28
+ systemd/logind). Otherwise creates a user-private subdirectory
29
+ under the system temp dir so sockets never live in a publicly
30
+ writable location.
31
+ """
32
+ xdg = os.environ.get("XDG_RUNTIME_DIR")
33
+ if xdg:
34
+ return xdg
35
+ fallback = os.path.join(
36
+ os.path.expanduser("~"),
37
+ ".culture",
38
+ "run",
39
+ )
40
+ os.makedirs(fallback, mode=0o700, exist_ok=True)
41
+ # Enforce permissions even if the directory already existed
42
+ os.chmod(fallback, stat.S_IRWXU)
43
+ return fallback
@@ -9,8 +9,10 @@ from culture.config import load_config_or_default
9
9
 
10
10
 
11
11
  def agent_socket_path(nick: str) -> str:
12
+ from culture.cli.shared.constants import culture_runtime_dir
13
+
12
14
  return os.path.join(
13
- os.environ.get("XDG_RUNTIME_DIR", "/tmp"),
15
+ culture_runtime_dir(),
14
16
  f"culture-{nick}.sock",
15
17
  )
16
18
 
@@ -15,6 +15,7 @@ from textual.widgets import Footer, Header
15
15
  from culture.aio import maybe_await
16
16
  from culture.console.client import ConsoleIRCClient
17
17
  from culture.console.commands import CommandType, parse_command
18
+ from culture.console.status import query_all_agents
18
19
  from culture.console.widgets.chat import ChatPanel
19
20
  from culture.console.widgets.info_panel import InfoPanel
20
21
  from culture.console.widgets.sidebar import ChannelItem, EntityItem, Sidebar
@@ -22,6 +23,7 @@ from culture.console.widgets.sidebar import ChannelItem, EntityItem, Sidebar
22
23
  logger = logging.getLogger(__name__)
23
24
 
24
25
  BUFFER_INTERVAL = 10.0 # seconds between UI refreshes
26
+ STATUS_POLL_INTERVAL = 30.0 # seconds between agent status polls
25
27
 
26
28
 
27
29
  class ConsoleApp(App):
@@ -33,10 +35,15 @@ class ConsoleApp(App):
33
35
  BINDINGS = [
34
36
  Binding("ctrl+o", "show_overview", "Overview", show=True),
35
37
  Binding("ctrl+s", "show_status", "Status", show=True),
38
+ Binding("f1", "show_help", "Help", show=True),
39
+ # Most terminals send 0x08 (backspace) for Ctrl+H, so this secondary
40
+ # bind only fires under terminals with modifyOtherKeys enabled.
41
+ Binding("ctrl+h", "show_help", "Help", show=False),
36
42
  Binding("escape", "back_to_chat", "Chat", show=True),
37
43
  Binding("ctrl+q", "quit_app", "Quit", show=True),
38
- Binding("tab", "next_channel", "Next channel", show=False),
39
- Binding("shift+tab", "prev_channel", "Prev channel", show=False),
44
+ # priority=True so Tab wins against Screen's default focus-cycling.
45
+ Binding("tab", "next_channel", "Next channel", show=False, priority=True),
46
+ Binding("shift+tab", "prev_channel", "Prev channel", show=False, priority=True),
40
47
  ]
41
48
 
42
49
  DEFAULT_CSS = """
@@ -65,6 +72,7 @@ class ConsoleApp(App):
65
72
 
66
73
  self._buffer_task: asyncio.Task | None = None
67
74
  self._background_tasks: set[asyncio.Task] = set()
75
+ self._status_poll_task: asyncio.Task | None = None
68
76
 
69
77
  # Dispatch table for command execution
70
78
  self._command_handlers: dict[CommandType, Any] = {
@@ -84,6 +92,7 @@ class ConsoleApp(App):
84
92
  CommandType.INVITE: self._handle_invite,
85
93
  CommandType.SERVER: self._handle_server,
86
94
  CommandType.QUIT: self._handle_quit,
95
+ CommandType.HELP: self._handle_help,
87
96
  }
88
97
 
89
98
  # ------------------------------------------------------------------
@@ -106,6 +115,7 @@ class ConsoleApp(App):
106
115
  """Set sub-title and kick off the buffer-drain loop."""
107
116
  self.sub_title = f"{self._client.nick}@{self._server_name}"
108
117
  self._buffer_task = asyncio.create_task(self._buffer_loop())
118
+ self._status_poll_task = asyncio.create_task(self._status_poll_loop())
109
119
 
110
120
  # Populate sidebar with any channels already joined at startup
111
121
  self._sync_sidebar()
@@ -140,6 +150,43 @@ class ConsoleApp(App):
140
150
  text=msg.text,
141
151
  )
142
152
 
153
+ async def _status_poll_loop(self) -> None:
154
+ """Periodically poll agent daemon sockets for status updates."""
155
+ # Initial poll on startup
156
+ await self._poll_agent_status()
157
+ while True:
158
+ try:
159
+ await asyncio.sleep(STATUS_POLL_INTERVAL)
160
+ await self._poll_agent_status()
161
+ except asyncio.CancelledError:
162
+ raise
163
+ except Exception:
164
+ logger.exception("Error in _status_poll_loop")
165
+
166
+ async def _poll_agent_status(self) -> None:
167
+ """Query daemon sockets and update sidebar entity activity."""
168
+ status_map = await query_all_agents()
169
+ if not status_map:
170
+ return
171
+ sidebar: Sidebar = self.query_one(Sidebar)
172
+ updated = False
173
+ new_entities = []
174
+ for ent in sidebar.entities:
175
+ if ent.nick in status_map:
176
+ new_ent = EntityItem(
177
+ nick=ent.nick,
178
+ entity_type=ent.entity_type,
179
+ online=ent.online,
180
+ icon=ent.icon,
181
+ activity=status_map[ent.nick],
182
+ )
183
+ new_entities.append(new_ent)
184
+ updated = True
185
+ else:
186
+ new_entities.append(ent)
187
+ if updated:
188
+ sidebar.entities = new_entities
189
+
143
190
  # ------------------------------------------------------------------
144
191
  # Input handler
145
192
  # ------------------------------------------------------------------
@@ -189,9 +236,8 @@ class ConsoleApp(App):
189
236
  return
190
237
  channel = cmd.args[0]
191
238
  await self._client.join(channel)
192
- self._current_channel = channel
193
239
  self._sync_sidebar()
194
- chat.set_channel(channel)
240
+ await self._switch_to_channel(channel)
195
241
  chat.add_message(time.time(), "", "system", f"Joined [bold]{channel}[/]")
196
242
 
197
243
  async def _handle_part(self, cmd) -> None: # noqa: ANN001
@@ -341,6 +387,59 @@ class ConsoleApp(App):
341
387
  async def _handle_quit(self, cmd) -> None: # noqa: ANN001
342
388
  await self.action_quit_app()
343
389
 
390
+ def _handle_help(self, cmd) -> None: # noqa: ANN001
391
+ self.action_show_help()
392
+
393
+ def action_show_help(self) -> None:
394
+ """Show help content with all commands and keybindings."""
395
+ self._current_view = "help"
396
+ chat: ChatPanel = self.query_one(ChatPanel)
397
+ lines = [
398
+ "[bold $warning]COMMANDS[/]",
399
+ "",
400
+ " [bold]/help[/] Show this help",
401
+ " [bold]/join[/] #channel Join a channel",
402
+ " [bold]/part[/] [#channel] Leave a channel",
403
+ " [bold]/read[/] [#ch] [-n N] Read channel history (default 50)",
404
+ " [bold]/who[/] [target] List channel members",
405
+ " [bold]/send[/] <target> <text> Send a direct message",
406
+ " [bold]/channels[/] List server channels",
407
+ " [bold]/agents[/] List visible agents",
408
+ " [bold]/status[/] [agent] Show status info",
409
+ " [bold]/overview[/] Show mesh overview",
410
+ " [bold]/icon[/] <emoji> Set your icon",
411
+ " [bold]/topic[/] #ch <text> Set channel topic",
412
+ " [bold]/kick[/] #ch <nick> Kick a user",
413
+ " [bold]/invite[/] <nick> #ch Invite a user",
414
+ " [bold]/server[/] [name] Switch server (restarts console)",
415
+ " [bold]/quit[/] Exit console",
416
+ "",
417
+ "[bold $warning]KEYBINDINGS[/]",
418
+ "",
419
+ " [bold]Tab / Shift+Tab[/] Cycle channels",
420
+ " [bold]Alt+←/→[/] Jump by word in input",
421
+ " [bold]Alt+Backspace[/] Delete previous word",
422
+ " [bold]Ctrl+O[/] Overview",
423
+ " [bold]Ctrl+S[/] Status",
424
+ " [bold]F1[/] Help (Ctrl+H on terminals that forward it)",
425
+ " [bold]Escape[/] Back to chat",
426
+ " [bold]Ctrl+Q[/] Quit",
427
+ "",
428
+ "[bold $warning]COPY-PASTE[/]",
429
+ "",
430
+ " Hold [bold]Shift[/] while dragging with the mouse to select text for copy-paste.",
431
+ " Most modern terminals (iTerm2, Kitty, Alacritty, WezTerm, GNOME Terminal,",
432
+ " Windows Terminal) let Shift bypass the TUI's mouse capture.",
433
+ ]
434
+ chat.set_content("Help", lines)
435
+
436
+ # Hide input — not meaningful in help view
437
+ try:
438
+ input_widget = self.query_one(self._CHAT_INPUT_ID)
439
+ input_widget.display = False
440
+ except Exception:
441
+ pass
442
+
344
443
  # ------------------------------------------------------------------
345
444
  # View actions
346
445
  # ------------------------------------------------------------------
@@ -468,20 +567,29 @@ class ConsoleApp(App):
468
567
  chat.set_content("Agents", lines)
469
568
 
470
569
  # Update sidebar entity roster
570
+ status_map = await query_all_agents()
471
571
  entity_items = [
472
- EntityItem(nick=nick, entity_type="agent", online=True) for nick in sorted(all_agents)
572
+ EntityItem(
573
+ nick=nick,
574
+ entity_type="agent",
575
+ online=True,
576
+ activity=status_map.get(nick, ""),
577
+ )
578
+ for nick in sorted(all_agents)
473
579
  ]
474
580
  sidebar.entities = entity_items
475
581
 
476
- def action_back_to_chat(self) -> None:
477
- """Return to the normal chat view."""
582
+ async def action_back_to_chat(self) -> None:
583
+ """Return to the normal chat view, reloading current channel history."""
478
584
  if self._current_view == "chat":
479
585
  return
586
+ if self._current_channel:
587
+ # Delegate: resets view, shows input, and reloads recent history —
588
+ # equivalent to running /read on the current channel.
589
+ await self._switch_to_channel(self._current_channel)
590
+ return
591
+ # No channel yet — just restore chat view and show the input.
480
592
  self._current_view = "chat"
481
- chat: ChatPanel = self.query_one(ChatPanel)
482
- chat.set_channel(self._current_channel)
483
-
484
- # Re-show input
485
593
  try:
486
594
  input_widget = self.query_one(self._CHAT_INPUT_ID)
487
595
  input_widget.display = True
@@ -506,16 +614,15 @@ class ConsoleApp(App):
506
614
  if not channels:
507
615
  return
508
616
  if self._current_channel not in channels:
509
- self._current_channel = channels[0]
617
+ target = channels[0]
510
618
  else:
511
619
  idx = channels.index(self._current_channel)
512
620
  idx = (idx + direction) % len(channels)
513
- self._current_channel = channels[idx]
621
+ target = channels[idx]
514
622
 
515
- chat: ChatPanel = self.query_one(ChatPanel)
516
- sidebar: Sidebar = self.query_one(Sidebar)
517
- chat.set_channel(self._current_channel)
518
- sidebar.active_channel = self._current_channel
623
+ task = asyncio.create_task(self._switch_to_channel(target))
624
+ self._background_tasks.add(task)
625
+ task.add_done_callback(self._background_tasks.discard)
519
626
 
520
627
  # ------------------------------------------------------------------
521
628
  # Quit
@@ -523,10 +630,14 @@ class ConsoleApp(App):
523
630
 
524
631
  async def action_quit_app(self) -> None:
525
632
  """Disconnect the IRC client and exit the app."""
526
- if self._buffer_task:
527
- self._buffer_task.cancel()
528
- await asyncio.gather(self._buffer_task, return_exceptions=True)
529
- self._buffer_task = None
633
+ for task_ref in (self._buffer_task, self._status_poll_task):
634
+ if task_ref:
635
+ task_ref.cancel()
636
+ tasks_to_cancel = [t for t in (self._buffer_task, self._status_poll_task) if t]
637
+ if tasks_to_cancel:
638
+ await asyncio.gather(*tasks_to_cancel, return_exceptions=True)
639
+ self._buffer_task = None
640
+ self._status_poll_task = None
530
641
 
531
642
  if self._client.connected:
532
643
  try:
@@ -542,19 +653,9 @@ class ConsoleApp(App):
542
653
 
543
654
  def on_sidebar_channel_selected(self, event: Sidebar.ChannelSelected) -> None:
544
655
  """Switch to the selected channel when user clicks sidebar."""
545
- self._current_channel = event.channel
546
- self._current_view = "chat"
547
- chat: ChatPanel = self.query_one(ChatPanel)
548
- sidebar: Sidebar = self.query_one(Sidebar)
549
- chat.set_channel(self._current_channel)
550
- sidebar.active_channel = self._current_channel
551
-
552
- # Re-show input if hidden
553
- try:
554
- input_widget = self.query_one(self._CHAT_INPUT_ID)
555
- input_widget.display = True
556
- except Exception:
557
- pass
656
+ task = asyncio.create_task(self._switch_to_channel(event.channel))
657
+ self._background_tasks.add(task)
658
+ task.add_done_callback(self._background_tasks.discard)
558
659
 
559
660
  def on_sidebar_entity_selected(self, event: Sidebar.EntitySelected) -> None:
560
661
  """Show agent detail when user clicks an entity in the sidebar."""
@@ -566,6 +667,41 @@ class ConsoleApp(App):
566
667
  # Internal helpers
567
668
  # ------------------------------------------------------------------
568
669
 
670
+ async def _switch_to_channel(self, channel: str) -> None:
671
+ """Switch to a channel, update UI, and auto-load recent history."""
672
+ if not channel:
673
+ return
674
+ # Guard against stale results from rapid switching
675
+ self._current_channel = channel
676
+ self._current_view = "chat"
677
+
678
+ sidebar: Sidebar = self.query_one(Sidebar)
679
+ chat: ChatPanel = self.query_one(ChatPanel)
680
+ sidebar.active_channel = channel
681
+ chat.set_channel(channel)
682
+ chat.clear_log()
683
+
684
+ # Re-show input if hidden (e.g., coming from overview/status view)
685
+ try:
686
+ input_widget = self.query_one(self._CHAT_INPUT_ID)
687
+ input_widget.display = True
688
+ except Exception:
689
+ pass
690
+
691
+ # Fetch recent history
692
+ entries = await self._client.history(channel, limit=20)
693
+ # Stale check: if user switched away during fetch, discard results
694
+ if self._current_channel != channel:
695
+ return
696
+ for e in entries:
697
+ try:
698
+ ts = float(e.get("timestamp", 0))
699
+ except (ValueError, TypeError):
700
+ ts = time.time()
701
+ chat.add_message(ts, "", e.get("nick", ""), e.get("text", ""))
702
+ if not entries:
703
+ chat.add_message(time.time(), "", "system", f"[dim]No history for {channel}[/]")
704
+
569
705
  def _sync_sidebar(self) -> None:
570
706
  """Sync the sidebar channel list from the client's joined_channels."""
571
707
  sidebar: Sidebar = self.query_one(Sidebar)
@@ -26,6 +26,7 @@ class CommandType(Enum):
26
26
  INVITE = auto()
27
27
  SERVER = auto()
28
28
  QUIT = auto()
29
+ HELP = auto()
29
30
  UNKNOWN = auto()
30
31
 
31
32
 
@@ -60,6 +61,7 @@ _COMMANDS: dict[str, CommandType] = {
60
61
  "invite": CommandType.INVITE,
61
62
  "server": CommandType.SERVER,
62
63
  "quit": CommandType.QUIT,
64
+ "help": CommandType.HELP,
63
65
  }
64
66
 
65
67
 
@@ -0,0 +1,74 @@
1
+ """Lightweight daemon IPC status queries for the console."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def discover_agent_sockets() -> list[tuple[str, Path]]:
13
+ """List culture daemon sockets in the culture runtime directory.
14
+
15
+ Returns a list of ``(nick, socket_path)`` tuples.
16
+ """
17
+ from culture.cli.shared.constants import culture_runtime_dir
18
+
19
+ runtime_dir = Path(culture_runtime_dir())
20
+ results: list[tuple[str, Path]] = []
21
+ if not runtime_dir.is_dir():
22
+ return results
23
+ for entry in runtime_dir.iterdir():
24
+ if entry.name.startswith("culture-") and entry.name.endswith(".sock") and entry.is_socket():
25
+ nick = entry.name[len("culture-") : -len(".sock")]
26
+ results.append((nick, entry))
27
+ return results
28
+
29
+
30
+ async def query_agent_status(socket_path: Path) -> dict:
31
+ """Query a single daemon socket for status (no LLM query).
32
+
33
+ Returns a dict with ``activity``, ``paused``, ``circuit_open``,
34
+ ``running`` keys, or an empty dict on failure.
35
+ """
36
+ from culture.cli.shared.ipc import ipc_request
37
+
38
+ resp = await ipc_request(str(socket_path), "status")
39
+ if resp is None or not resp.get("ok"):
40
+ return {}
41
+ return resp.get("data", {})
42
+
43
+
44
+ def _derive_activity(data: dict) -> str:
45
+ """Derive a single activity string from daemon status fields."""
46
+ if data.get("circuit_open"):
47
+ return "circuit-open"
48
+ if data.get("paused"):
49
+ return "paused"
50
+ if not data.get("running"):
51
+ return "idle"
52
+ return data.get("activity", "idle")
53
+
54
+
55
+ async def query_all_agents() -> dict[str, str]:
56
+ """Query all local daemon sockets and return nick -> activity mapping."""
57
+ import asyncio
58
+
59
+ sockets = discover_agent_sockets()
60
+ if not sockets:
61
+ return {}
62
+
63
+ results: dict[str, str] = {}
64
+
65
+ async def _query_one(nick: str, path: Path) -> None:
66
+ try:
67
+ data = await asyncio.wait_for(query_agent_status(path), timeout=3.0)
68
+ if data:
69
+ results[nick] = _derive_activity(data)
70
+ except Exception:
71
+ logger.debug("Failed to query status for %s", nick)
72
+
73
+ await asyncio.gather(*[_query_one(nick, path) for nick, path in sockets])
74
+ return results
@@ -5,12 +5,35 @@ from __future__ import annotations
5
5
  from datetime import datetime
6
6
 
7
7
  from textual.app import ComposeResult
8
+ from textual.binding import Binding
8
9
  from textual.containers import Vertical
9
10
  from textual.message import Message
10
11
  from textual.widget import Widget
11
12
  from textual.widgets import Input, RichLog, Static
12
13
 
13
14
 
15
+ class ChatInput(Input):
16
+ """Input with Alt+Arrow word-jump and Alt+Backspace word-delete."""
17
+
18
+ BINDINGS = [
19
+ Binding("alt+left", "cursor_left_word", "Word left", show=False),
20
+ Binding("alt+right", "cursor_right_word", "Word right", show=False),
21
+ Binding(
22
+ "alt+shift+left",
23
+ "cursor_left_word(True)",
24
+ "Select word left",
25
+ show=False,
26
+ ),
27
+ Binding(
28
+ "alt+shift+right",
29
+ "cursor_right_word(True)",
30
+ "Select word right",
31
+ show=False,
32
+ ),
33
+ Binding("alt+backspace", "delete_left_word", "Delete word", show=False),
34
+ ]
35
+
36
+
14
37
  class ChatPanel(Widget):
15
38
  """Center panel showing the message log and an input field.
16
39
 
@@ -75,7 +98,7 @@ class ChatPanel(Widget):
75
98
  yield Static("", id="chat-header")
76
99
  with Vertical():
77
100
  yield RichLog(id="chat-log", markup=True, wrap=True, highlight=False)
78
- yield Input(placeholder="Type a message or /command…", id="chat-input")
101
+ yield ChatInput(placeholder="Type a message or /command…", id="chat-input")
79
102
 
80
103
  def on_mount(self) -> None:
81
104
  self._channel = ""
@@ -32,6 +32,7 @@ class EntityItem:
32
32
  entity_type: str = "agent" # "agent" | "admin" | "human" | "bot"
33
33
  online: bool = True
34
34
  icon: str = ""
35
+ activity: str = "" # "working" | "idle" | "paused" | "circuit-open" | ""
35
36
 
36
37
 
37
38
  # ---------------------------------------------------------------------------
@@ -46,6 +47,14 @@ _TYPE_ICON: dict[str, str] = {
46
47
  "bot": "⚙",
47
48
  }
48
49
 
50
+ # Activity indicators for agent status
51
+ _ACTIVITY_INDICATOR: dict[str, str] = {
52
+ "working": "[green]●[/]",
53
+ "idle": "[dim]○[/]",
54
+ "paused": "[yellow]⏸[/]",
55
+ "circuit-open": "[red]⚠[/]",
56
+ }
57
+
49
58
  # Group order for entity types
50
59
  _GROUP_ORDER = ["agent", "admin", "human", "bot"]
51
60
 
@@ -105,7 +114,12 @@ class _EntityRow(Static):
105
114
  """
106
115
 
107
116
  def __init__(self, entity: EntityItem) -> None:
108
- dot = "[green]●[/]" if entity.online else "[dim]○[/]"
117
+ if entity.activity and entity.activity in _ACTIVITY_INDICATOR:
118
+ dot = _ACTIVITY_INDICATOR[entity.activity]
119
+ elif entity.online:
120
+ dot = "●"
121
+ else:
122
+ dot = "[dim]○[/]"
109
123
  icon = entity.icon or _TYPE_ICON.get(entity.entity_type, "")
110
124
  markup = f"{dot} {icon} {entity.nick}"
111
125
  super().__init__(markup, markup=True)