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