agentirc-cli 6.2.0__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.
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/CHANGELOG.md +16 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/PKG-INFO +1 -1
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/channel.py +45 -2
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/app.py +24 -10
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/widgets/chat.py +24 -1
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/observer.py +16 -3
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/cli/index.md +17 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/pyproject.toml +1 -1
- agentirc_cli-6.2.1/tests/test_console_fixes_224_227.py +199 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/uv.lock +1 -1
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.claude/skills/run-tests/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.flake8 +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.github/workflows/docs-check.yml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.gitignore +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.pr_agent.toml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/.pylintrc +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/CLAUDE.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/Gemfile +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/Gemfile.lock +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/LICENSE +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/README.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/SECURITY.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_config.agentirc.yml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_config.base.yml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_config.culture.yml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_data/sites.yml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_includes/head_custom.html +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_plugins/site_filter.rb +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_sass/color_schemes/dark-terminal.scss +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/assets/images/IMG_3183.png +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/assets/images/apple-touch-icon.png +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/assets/images/favicon-16x16.png +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/assets/images/favicon-32x32.png +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/assets/images/favicon.ico +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/assets/images/og-agentirc.png +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/assets/images/og-culture.png +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/__main__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/CLAUDE.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/__main__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/channel.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/docs/agentirc-architecture.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/docs/agentirc-features.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/docs/agentirc-skill.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/docs/agentirc.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/history_store.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/ircd.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/remote_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/room_store.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/rooms_util.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/server_link.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/skill.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/skills/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/skills/history.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/skills/icon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/skills/rooms.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/skills/threads.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/agentirc/thread_store.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/aio.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/bots/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/bots/bot.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/bots/config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/agent.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/bot.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/mesh.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/server.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/shared/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/shared/constants.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/shared/display.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/shared/formatting.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/shared/ipc.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/shared/mesh.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/shared/process.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/cli/skills.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/agent_runner.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/culture.yaml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/irc_transport.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/message_buffer.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/socket_server.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/__main__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/culture.yaml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/irc_transport.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/socket_server.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/culture.yaml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/irc_transport.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/socket_server.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/culture.yaml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/irc_transport.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/commands.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/status.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/widgets/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/widgets/info_panel.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/console/widgets/sidebar.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/credentials.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/formatting.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/learn_prompt.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/mesh_config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/overview/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/overview/collector.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/overview/model.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/overview/renderer_text.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/overview/renderer_web.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/overview/web/style.css +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/persistence.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/pidfile.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/commands.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/extensions/icons.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/message.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/protocol/replies.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/culture/skills/culture/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/README.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/agentirc/architecture-overview.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/agentirc/index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/agentirc/why-agentirc.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/agent-lifecycle.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/choose-a-harness.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/mental-model.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/operate.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/patterns.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/quickstart.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/reflective-development.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/vision-patterns-index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/vision.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/culture/why-culture.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/architecture/agent-harness-spec.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/architecture/index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/architecture/layers.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/architecture/threads.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/cli/commands.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/harnesses/acp.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/harnesses/claude.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/harnesses/codex.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/harnesses/copilot.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/harnesses/index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/server/architecture.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/server/config.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/server/deployment.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/server/index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/reference/server/security.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/concepts/federation.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/concepts/harnesses.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/concepts/humans-and-agents.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/concepts/index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/concepts/persistence.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/concepts/rooms.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/demos/magic-demo.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/guides/first-session.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/guides/index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/guides/join-as-human.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/guides/local-setup.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/guides/multi-machine.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases/10-agent-lifecycle.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/shared/use-cases-index.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/plans/2026-04-12-console-enhancements.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/docs/superpowers/specs/2026-04-12-console-enhancements-design.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/favicon.ico +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/culture.yaml +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/irc_transport.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/plugins/claude-code/skills/culture/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/robots.txt +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/sonar-project.properties +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/__init__.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/conftest.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_archive.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_bot.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_bot_config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_channel.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_channel_cli.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_connection.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_console_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_console_commands.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_console_connection.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_console_icons.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_console_integration.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_console_status.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_credentials.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_culture_config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_daemon.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_discovery.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_display.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_federation.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_history.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_http_listener.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_ipc.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_learn_prompt.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_manifest_config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_mention_alias.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_mention_target_cleanup.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_mention_warning.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_mentions.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_mesh_readiness.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_message.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_messaging.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_migrate_cli.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_modes.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_overview_model.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_overview_web.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_persistence.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_pidfile.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_poll_loop.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_register_cli.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_rooms.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_setup_update_cli.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_skill_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_skill_docs.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_skills.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_socket_server.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_supervisor.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_template_engine.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_threads.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_wait_for_port.py +0 -0
- {agentirc_cli-6.2.0 → agentirc_cli-6.2.1}/tests/test_webhook.py +0 -0
|
@@ -4,6 +4,22 @@ 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
|
+
|
|
7
23
|
## [6.2.0] - 2026-04-12
|
|
8
24
|
|
|
9
25
|
|
|
@@ -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=
|
|
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,
|
|
269
|
+
asyncio.run(observer.send_message(target, text))
|
|
227
270
|
print(f"Sent to {target}")
|
|
228
271
|
|
|
229
272
|
|
|
@@ -35,11 +35,15 @@ class ConsoleApp(App):
|
|
|
35
35
|
BINDINGS = [
|
|
36
36
|
Binding("ctrl+o", "show_overview", "Overview", show=True),
|
|
37
37
|
Binding("ctrl+s", "show_status", "Status", show=True),
|
|
38
|
-
Binding("
|
|
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),
|
|
39
42
|
Binding("escape", "back_to_chat", "Chat", show=True),
|
|
40
43
|
Binding("ctrl+q", "quit_app", "Quit", show=True),
|
|
41
|
-
|
|
42
|
-
Binding("
|
|
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),
|
|
43
47
|
]
|
|
44
48
|
|
|
45
49
|
DEFAULT_CSS = """
|
|
@@ -413,11 +417,19 @@ class ConsoleApp(App):
|
|
|
413
417
|
"[bold $warning]KEYBINDINGS[/]",
|
|
414
418
|
"",
|
|
415
419
|
" [bold]Tab / Shift+Tab[/] Cycle channels",
|
|
420
|
+
" [bold]Alt+←/→[/] Jump by word in input",
|
|
421
|
+
" [bold]Alt+Backspace[/] Delete previous word",
|
|
416
422
|
" [bold]Ctrl+O[/] Overview",
|
|
417
423
|
" [bold]Ctrl+S[/] Status",
|
|
418
|
-
" [bold]
|
|
424
|
+
" [bold]F1[/] Help (Ctrl+H on terminals that forward it)",
|
|
419
425
|
" [bold]Escape[/] Back to chat",
|
|
420
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.",
|
|
421
433
|
]
|
|
422
434
|
chat.set_content("Help", lines)
|
|
423
435
|
|
|
@@ -567,15 +579,17 @@ class ConsoleApp(App):
|
|
|
567
579
|
]
|
|
568
580
|
sidebar.entities = entity_items
|
|
569
581
|
|
|
570
|
-
def action_back_to_chat(self) -> None:
|
|
571
|
-
"""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."""
|
|
572
584
|
if self._current_view == "chat":
|
|
573
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.
|
|
574
592
|
self._current_view = "chat"
|
|
575
|
-
chat: ChatPanel = self.query_one(ChatPanel)
|
|
576
|
-
chat.set_channel(self._current_channel)
|
|
577
|
-
|
|
578
|
-
# Re-show input
|
|
579
593
|
try:
|
|
580
594
|
input_widget = self.query_one(self._CHAT_INPUT_ID)
|
|
581
595
|
input_widget.display = True
|
|
@@ -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
|
|
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 = ""
|
|
@@ -205,11 +205,23 @@ class IRCObserver:
|
|
|
205
205
|
async def send_message(self, target: str, text: str) -> None:
|
|
206
206
|
"""Send a PRIVMSG to a channel or nick, then disconnect.
|
|
207
207
|
|
|
208
|
+
``text`` is split on real ``\\n`` bytes into one PRIVMSG per line,
|
|
209
|
+
since an IRC PRIVMSG must be single-line per RFC 2812. Empty lines
|
|
210
|
+
(and any embedded ``\\r``) are dropped — IRC can't carry an empty
|
|
211
|
+
PRIVMSG body, and this keeps multi-line output from emitting no-op
|
|
212
|
+
frames. If every line is empty, the method returns without
|
|
213
|
+
connecting.
|
|
214
|
+
|
|
208
215
|
Uses the same ephemeral connection pattern as the read commands.
|
|
209
216
|
"""
|
|
210
|
-
#
|
|
217
|
+
# Strip CR and LF from the target to prevent IRC command injection
|
|
218
|
+
# (a newline in the target would let an attacker smuggle a second
|
|
219
|
+
# protocol line).
|
|
211
220
|
target = target.replace("\r", "").replace("\n", "")
|
|
212
|
-
|
|
221
|
+
# Split on real newlines; drop empty lines and strip CRs
|
|
222
|
+
lines = [ln for ln in text.replace("\r", "").split("\n") if ln]
|
|
223
|
+
if not lines:
|
|
224
|
+
return
|
|
213
225
|
|
|
214
226
|
reader, writer, nick = await self._connect_and_register()
|
|
215
227
|
try:
|
|
@@ -220,7 +232,8 @@ class IRCObserver:
|
|
|
220
232
|
# Drain join responses
|
|
221
233
|
await self._recv_lines(reader, timeout=1.0)
|
|
222
234
|
|
|
223
|
-
|
|
235
|
+
for line in lines:
|
|
236
|
+
writer.write(f"PRIVMSG {target} :{line}\r\n".encode())
|
|
224
237
|
await writer.drain()
|
|
225
238
|
finally:
|
|
226
239
|
await self._disconnect(writer)
|
|
@@ -244,6 +244,23 @@ culture channel message "#general" "hello from the CLI"
|
|
|
244
244
|
|
|
245
245
|
Uses an ephemeral IRC connection — no daemon required.
|
|
246
246
|
|
|
247
|
+
**Multi-line messages.** The message text interprets `\n` as a newline and
|
|
248
|
+
`\t` as a tab, so the shell can pass multi-line input without needing
|
|
249
|
+
`$'...'` quoting. Each line is sent as a separate IRC `PRIVMSG` (required by
|
|
250
|
+
RFC 2812 — a single `PRIVMSG` can't span lines). Empty lines are dropped.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
culture channel message "#general" "line one\nline two\nline three"
|
|
254
|
+
# → three separate PRIVMSG lines on the channel
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
To send a literal backslash-n (two characters) escape the backslash:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
culture channel message "#general" "use \\n in your string"
|
|
261
|
+
# → sends the text: use \n in your string
|
|
262
|
+
```
|
|
263
|
+
|
|
247
264
|
### `culture agent message`
|
|
248
265
|
|
|
249
266
|
Send a message directly to an agent.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Tests for console fixes covering issues #224, #225, #226, #227.
|
|
2
|
+
|
|
3
|
+
- #224 `/read` on exit overview + mouse copy-paste documentation
|
|
4
|
+
- #225 Newlines in IRC messages — CLI escape interpretation + observer split
|
|
5
|
+
- #226 Alt+Arrow word-jump (and Alt+Backspace) in chat input
|
|
6
|
+
- #227 Tab cycles channels (priority binding over Screen focus-cycling)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from culture.cli.channel import _cmd_message, _interpret_escapes
|
|
16
|
+
from culture.console.app import ConsoleApp
|
|
17
|
+
from culture.console.widgets.chat import ChatInput
|
|
18
|
+
from culture.observer import IRCObserver
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# #225 — CLI escape interpretation
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestInterpretEscapes:
|
|
26
|
+
"""Convert shell-literal \\n / \\t / \\\\ sequences to real chars."""
|
|
27
|
+
|
|
28
|
+
def test_single_newline(self):
|
|
29
|
+
assert _interpret_escapes(r"a\nb") == "a\nb"
|
|
30
|
+
|
|
31
|
+
def test_multiple_newlines(self):
|
|
32
|
+
assert _interpret_escapes(r"a\nb\nc") == "a\nb\nc"
|
|
33
|
+
|
|
34
|
+
def test_tab(self):
|
|
35
|
+
assert _interpret_escapes(r"col1\tcol2") == "col1\tcol2"
|
|
36
|
+
|
|
37
|
+
def test_mixed(self):
|
|
38
|
+
assert _interpret_escapes(r"line1\n\tindented") == "line1\n\tindented"
|
|
39
|
+
|
|
40
|
+
def test_no_escapes(self):
|
|
41
|
+
assert _interpret_escapes("plain text") == "plain text"
|
|
42
|
+
|
|
43
|
+
def test_real_newlines_preserved(self):
|
|
44
|
+
# Real newlines (e.g., from heredoc) passthrough unchanged.
|
|
45
|
+
assert _interpret_escapes("a\nb") == "a\nb"
|
|
46
|
+
|
|
47
|
+
def test_double_backslash_is_literal_backslash(self):
|
|
48
|
+
# \\ collapses to a single backslash — so \\n stays as literal
|
|
49
|
+
# backslash-n (two chars), not as a newline.
|
|
50
|
+
assert _interpret_escapes(r"a\\b") == r"a\b"
|
|
51
|
+
assert _interpret_escapes(r"use \\n in your text") == r"use \n in your text"
|
|
52
|
+
|
|
53
|
+
def test_other_escapes_untouched(self):
|
|
54
|
+
# Only \n, \t, \\ are recognised. Any other \x pair is passed through
|
|
55
|
+
# with the backslash intact — no \x.. / \u.... / \r interpretation.
|
|
56
|
+
assert _interpret_escapes(r"a\fb") == r"a\fb"
|
|
57
|
+
assert _interpret_escapes(r"a\rb") == r"a\rb"
|
|
58
|
+
assert _interpret_escapes(r"a\xb") == r"a\xb"
|
|
59
|
+
# Trailing lone backslash passes through
|
|
60
|
+
assert _interpret_escapes("a\\") == "a\\"
|
|
61
|
+
|
|
62
|
+
def test_interpreted_newline_is_real(self):
|
|
63
|
+
# The literal \n we produce is a real newline, not a backslash-n.
|
|
64
|
+
assert "\\n" not in _interpret_escapes(r"a\nb")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TestMessageEmptyAfterInterpret:
|
|
68
|
+
"""_cmd_message must exit 1 if nothing would be sent after interpretation."""
|
|
69
|
+
|
|
70
|
+
def _args(self, text: str) -> argparse.Namespace:
|
|
71
|
+
return argparse.Namespace(
|
|
72
|
+
target="#test",
|
|
73
|
+
text=text,
|
|
74
|
+
config="~/.culture/server.yaml",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def test_only_whitespace_and_newlines_rejected(self, capsys):
|
|
78
|
+
# Pre-interpretation pass (non-empty string), but after interpretation
|
|
79
|
+
# the message is all empty lines — nothing would actually be sent.
|
|
80
|
+
with pytest.raises(SystemExit) as exc:
|
|
81
|
+
_cmd_message(self._args(r"\n\n\n"))
|
|
82
|
+
assert exc.value.code == 1
|
|
83
|
+
assert "no non-empty line" in capsys.readouterr().err
|
|
84
|
+
|
|
85
|
+
def test_whitespace_only_rejected_before_interpretation(self, capsys):
|
|
86
|
+
# The existing strip check rejects pure whitespace before we get to
|
|
87
|
+
# the interpretation step.
|
|
88
|
+
with pytest.raises(SystemExit) as exc:
|
|
89
|
+
_cmd_message(self._args(" "))
|
|
90
|
+
assert exc.value.code == 1
|
|
91
|
+
assert "cannot be empty" in capsys.readouterr().err
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# #225 — observer multi-line PRIVMSG
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.asyncio
|
|
100
|
+
async def test_observer_send_message_splits_newlines(server, make_client):
|
|
101
|
+
"""Issue #225: send_message emits one PRIVMSG per newline."""
|
|
102
|
+
# Receiver joins and listens
|
|
103
|
+
receiver = await make_client(nick="testserv-recv", user="recv")
|
|
104
|
+
await receiver.send("JOIN #multiline")
|
|
105
|
+
await receiver.recv_all(timeout=0.5)
|
|
106
|
+
|
|
107
|
+
observer = IRCObserver(
|
|
108
|
+
host="127.0.0.1",
|
|
109
|
+
port=server.config.port,
|
|
110
|
+
server_name=server.config.name,
|
|
111
|
+
)
|
|
112
|
+
await observer.send_message("#multiline", "line one\nline two\nline three")
|
|
113
|
+
|
|
114
|
+
lines = await receiver.recv_all(timeout=1.0)
|
|
115
|
+
joined = "\n".join(lines)
|
|
116
|
+
|
|
117
|
+
# Each line must arrive as a separate PRIVMSG
|
|
118
|
+
assert joined.count("PRIVMSG #multiline") >= 3
|
|
119
|
+
assert "line one" in joined
|
|
120
|
+
assert "line two" in joined
|
|
121
|
+
assert "line three" in joined
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@pytest.mark.asyncio
|
|
125
|
+
async def test_observer_send_message_drops_empty_lines(server, make_client):
|
|
126
|
+
"""Empty lines between real lines are dropped (IRC can't send empty PRIVMSG)."""
|
|
127
|
+
receiver = await make_client(nick="testserv-recv2", user="recv2")
|
|
128
|
+
await receiver.send("JOIN #drops")
|
|
129
|
+
await receiver.recv_all(timeout=0.5)
|
|
130
|
+
|
|
131
|
+
observer = IRCObserver(
|
|
132
|
+
host="127.0.0.1",
|
|
133
|
+
port=server.config.port,
|
|
134
|
+
server_name=server.config.name,
|
|
135
|
+
)
|
|
136
|
+
await observer.send_message("#drops", "a\n\n\nb")
|
|
137
|
+
|
|
138
|
+
lines = await receiver.recv_all(timeout=1.0)
|
|
139
|
+
joined = "\n".join(lines)
|
|
140
|
+
# Exactly two PRIVMSGs — the three blank segments are skipped
|
|
141
|
+
assert joined.count("PRIVMSG #drops") == 2
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
# #226 — ChatInput bindings
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestChatInputBindings:
|
|
150
|
+
"""ChatInput must expose Alt+Arrow word-jump and Alt+Backspace."""
|
|
151
|
+
|
|
152
|
+
def _binding_map(self) -> dict[str, str]:
|
|
153
|
+
return {b.key: b.action for b in ChatInput.BINDINGS}
|
|
154
|
+
|
|
155
|
+
def test_alt_left_word_jump(self):
|
|
156
|
+
assert self._binding_map().get("alt+left") == "cursor_left_word"
|
|
157
|
+
|
|
158
|
+
def test_alt_right_word_jump(self):
|
|
159
|
+
assert self._binding_map().get("alt+right") == "cursor_right_word"
|
|
160
|
+
|
|
161
|
+
def test_alt_shift_selects_word(self):
|
|
162
|
+
m = self._binding_map()
|
|
163
|
+
assert m.get("alt+shift+left") == "cursor_left_word(True)"
|
|
164
|
+
assert m.get("alt+shift+right") == "cursor_right_word(True)"
|
|
165
|
+
|
|
166
|
+
def test_alt_backspace_deletes_word(self):
|
|
167
|
+
assert self._binding_map().get("alt+backspace") == "delete_left_word"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
# #227 — Tab cycles channels (priority binding)
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class TestConsoleAppBindings:
|
|
176
|
+
"""App-level bindings for #227 and the Ctrl+H help follow-up."""
|
|
177
|
+
|
|
178
|
+
def _bindings(self):
|
|
179
|
+
return {b.key: b for b in ConsoleApp.BINDINGS}
|
|
180
|
+
|
|
181
|
+
def test_tab_cycles_next_channel(self):
|
|
182
|
+
b = self._bindings()["tab"]
|
|
183
|
+
assert b.action == "next_channel"
|
|
184
|
+
assert b.priority is True, "Tab must use priority to beat Screen focus-cycling"
|
|
185
|
+
|
|
186
|
+
def test_shift_tab_cycles_prev_channel(self):
|
|
187
|
+
b = self._bindings()["shift+tab"]
|
|
188
|
+
assert b.action == "prev_channel"
|
|
189
|
+
assert b.priority is True
|
|
190
|
+
|
|
191
|
+
def test_help_is_bound_to_f1(self):
|
|
192
|
+
# F1 is the primary help key — most terminals eat Ctrl+H as backspace.
|
|
193
|
+
f1 = self._bindings()["f1"]
|
|
194
|
+
assert f1.action == "show_help"
|
|
195
|
+
|
|
196
|
+
def test_ctrl_h_remains_as_secondary_help(self):
|
|
197
|
+
# Secondary binding still present for terminals with modifyOtherKeys.
|
|
198
|
+
ctrl_h = self._bindings()["ctrl+h"]
|
|
199
|
+
assert ctrl_h.action == "show_help"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|