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