agentirc-cli 3.1.1__tar.gz → 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/CHANGELOG.md +22 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/PKG-INFO +1 -1
- agentirc_cli-4.0.0/culture/cli/__init__.py +58 -0
- agentirc_cli-4.0.0/culture/cli/_helpers.py +427 -0
- agentirc_cli-4.0.0/culture/cli/agent.py +740 -0
- agentirc_cli-4.0.0/culture/cli/bot.py +203 -0
- agentirc_cli-4.0.0/culture/cli/channel.py +108 -0
- agentirc_cli-4.0.0/culture/cli/mesh.py +571 -0
- agentirc_cli-4.0.0/culture/cli/server.py +365 -0
- agentirc_cli-4.0.0/culture/cli/skills.py +143 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/__main__.py +2 -2
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/learn_prompt.py +8 -8
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/skills/culture/SKILL.md +36 -36
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/acp/overview.md +5 -5
- agentirc_cli-4.0.0/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +212 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/pyproject.toml +1 -1
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_overview_cli.py +14 -10
- agentirc_cli-4.0.0/tests/test_setup_update_cli.py +45 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_wait_for_port.py +1 -1
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/uv.lock +1 -1
- agentirc_cli-3.1.1/culture/cli.py +0 -2432
- agentirc_cli-3.1.1/tests/test_setup_update_cli.py +0 -44
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.flake8 +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.github/workflows/security-checks.yml +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.gitignore +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.pr_agent.toml +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.pre-commit-config.yaml +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/.pylintrc +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/CLAUDE.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/CNAME +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/Gemfile +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/Gemfile.lock +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/LICENSE +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/README.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/SECURITY.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/_config.yml +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/__main__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/bots/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/bots/bot.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/bots/bot_manager.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/bots/config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/bots/http_listener.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/bots/template_engine.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/bots/virtual_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/agent_runner.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/ipc.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/irc_transport.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/message_buffer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/skill/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/skill/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/skill/irc_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/socket_server.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/supervisor.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/acp/webhook.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/ipc.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/irc_transport.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/socket_server.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/supervisor.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/claude/webhook.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/ipc.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/irc_transport.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/socket_server.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/supervisor.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/codex/webhook.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/ipc.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/irc_transport.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/clients/copilot/webhook.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/console/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/console/app.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/console/client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/console/commands.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/console/widgets/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/console/widgets/chat.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/console/widgets/info_panel.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/console/widgets/sidebar.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/credentials.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/mesh_config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/observer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/overview/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/overview/collector.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/overview/model.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/overview/renderer_text.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/overview/renderer_web.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/overview/web/style.css +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/persistence.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/pidfile.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/commands.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/extensions/federation.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/extensions/history.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/extensions/icons.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/extensions/tags.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/extensions/threads.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/message.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/protocol-index.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/protocol/replies.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/__main__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/channel.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/ircd.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/remote_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/room_store.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/rooms_util.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/server_link.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/skill.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/skills/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/skills/history.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/skills/icon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/skills/rooms.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/skills/threads.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/culture/server/thread_store.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/agent-lifecycle.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/agentic-self-learn.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/agent-client.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/agent-harness-spec.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/harness-conformance.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/index.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/layer1-core-irc.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/layer2-attention.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/layer3-skills.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/layer4-federation.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/layer5-agent-harness.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/server-architecture.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/architecture/threads.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/culture-cli.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/getting-started.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/index.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/SECURITY.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/bots.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/ci.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/cli.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/docs-site.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/index.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/ops-tooling.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/overview.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/operations/publishing.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/rooms.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/server-rename.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases/10-agent-lifecycle.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/use-cases-index.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/docs/what-is-culture.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/irc_transport.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/plugins/claude-code/skills/culture/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/sonar-project.properties +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/__init__.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/conftest.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_acp_daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_bot.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_bot_config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_bot_manager.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_bots_integration.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_channel.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_connection.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_console_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_console_commands.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_console_connection.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_console_icons.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_console_integration.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_daemon.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_discovery.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_federation.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_history.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_http_listener.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_ipc.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_link_reconnect.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_mentions.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_mesh_config.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_message.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_messaging.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_modes.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_overview_model.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_overview_web.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_persistence.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_pidfile.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_rooms.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_server_icon_skill.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_skill_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_skills.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_socket_server.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_supervisor.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_template_engine.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_thread_buffer.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_threads.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_virtual_client.py +0 -0
- {agentirc_cli-3.1.1 → agentirc_cli-4.0.0}/tests/test_webhook.py +0 -0
|
@@ -4,6 +4,28 @@ 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
|
+
## [4.0.0] - 2026-04-06
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- culture agent message and culture agent read for DM operations
|
|
13
|
+
- culture channel message and culture channel who for channel operations
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Reorganized CLI into noun-first command groups: agent, server, mesh, channel, bot, skills
|
|
19
|
+
- Split monolithic cli.py (2432 lines) into focused modules under culture/cli/
|
|
20
|
+
- Mirrored message and read commands under both agent and channel groups
|
|
21
|
+
|
|
22
|
+
## [3.1.2] - 2026-04-06
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- culture update used wrong package name (culture-cli) for uv tool upgrade
|
|
28
|
+
|
|
7
29
|
## [3.1.1] - 2026-04-06
|
|
8
30
|
|
|
9
31
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Unified CLI entry point for culture.
|
|
2
|
+
|
|
3
|
+
Commands are organized into noun-based groups:
|
|
4
|
+
culture agent {create,join,start,stop,status,rename,assign,sleep,wake,learn,message,read}
|
|
5
|
+
culture server {start,stop,status,default,rename}
|
|
6
|
+
culture mesh {overview,setup,update,console}
|
|
7
|
+
culture channel {list,read,message,who}
|
|
8
|
+
culture bot {create,start,stop,list,inspect}
|
|
9
|
+
culture skills {install}
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import logging
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from culture.cli import agent, bot, channel, mesh, server, skills
|
|
19
|
+
|
|
20
|
+
GROUPS = [agent, server, mesh, channel, bot, skills]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="culture",
|
|
26
|
+
description="culture — AI agent IRC mesh",
|
|
27
|
+
)
|
|
28
|
+
sub = parser.add_subparsers(dest="command")
|
|
29
|
+
for group in GROUPS:
|
|
30
|
+
group.register(sub)
|
|
31
|
+
return parser
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main() -> None:
|
|
35
|
+
parser = _build_parser()
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
if args.command is None:
|
|
39
|
+
parser.print_help()
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
logging.basicConfig(
|
|
43
|
+
level=logging.INFO,
|
|
44
|
+
format="%(asctime)s %(name)s %(levelname)s %(message)s",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
for group in GROUPS:
|
|
49
|
+
if args.command == group.NAME:
|
|
50
|
+
group.dispatch(args)
|
|
51
|
+
return
|
|
52
|
+
parser.print_help()
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
except KeyboardInterrupt:
|
|
55
|
+
pass
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
58
|
+
sys.exit(1)
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"""Shared helpers for culture CLI modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import signal
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
from culture.clients.claude.config import (
|
|
14
|
+
load_config,
|
|
15
|
+
load_config_or_default,
|
|
16
|
+
)
|
|
17
|
+
from culture.pidfile import (
|
|
18
|
+
is_culture_process,
|
|
19
|
+
is_process_alive,
|
|
20
|
+
read_pid,
|
|
21
|
+
remove_pid,
|
|
22
|
+
write_pid,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("culture")
|
|
26
|
+
|
|
27
|
+
DEFAULT_CONFIG = os.path.expanduser("~/.culture/agents.yaml")
|
|
28
|
+
_CONFIG_HELP = "Config file path"
|
|
29
|
+
LOG_DIR = os.path.expanduser("~/.culture/logs")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# -----------------------------------------------------------------------
|
|
33
|
+
# Link / credential helpers
|
|
34
|
+
# -----------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_link(value: str):
|
|
38
|
+
"""Parse a link spec: name:host:port:password[:trust]
|
|
39
|
+
|
|
40
|
+
Trust is extracted from the end if it matches a known value.
|
|
41
|
+
This allows passwords containing colons.
|
|
42
|
+
"""
|
|
43
|
+
from culture.server.config import LinkConfig
|
|
44
|
+
|
|
45
|
+
trust = "full"
|
|
46
|
+
if value.endswith(":full") or value.endswith(":restricted"):
|
|
47
|
+
value, trust = value.rsplit(":", 1)
|
|
48
|
+
|
|
49
|
+
parts = value.split(":", 3)
|
|
50
|
+
if len(parts) != 4:
|
|
51
|
+
raise argparse.ArgumentTypeError(
|
|
52
|
+
f"Link must be name:host:port:password[:trust], got: {value}"
|
|
53
|
+
)
|
|
54
|
+
name, host, port_str, password = parts
|
|
55
|
+
try:
|
|
56
|
+
port = int(port_str)
|
|
57
|
+
except ValueError:
|
|
58
|
+
raise argparse.ArgumentTypeError(f"Invalid port: {port_str}")
|
|
59
|
+
return LinkConfig(name=name, host=host, port=port, password=password, trust=trust)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def resolve_links_from_mesh(mesh_config_path: str) -> list:
|
|
63
|
+
"""Load link configs from mesh.yaml, looking up passwords from OS keyring."""
|
|
64
|
+
from culture.credentials import lookup_credential
|
|
65
|
+
from culture.mesh_config import load_mesh_config
|
|
66
|
+
from culture.server.config import LinkConfig
|
|
67
|
+
|
|
68
|
+
mesh = load_mesh_config(mesh_config_path)
|
|
69
|
+
links = []
|
|
70
|
+
for lc in mesh.server.links:
|
|
71
|
+
password = lookup_credential(lc.name)
|
|
72
|
+
if not password:
|
|
73
|
+
logger.warning(
|
|
74
|
+
"No credential found for peer '%s' — link will not be established. "
|
|
75
|
+
"Run 'culture mesh setup' to store link passwords.",
|
|
76
|
+
lc.name,
|
|
77
|
+
)
|
|
78
|
+
continue
|
|
79
|
+
links.append(
|
|
80
|
+
LinkConfig(
|
|
81
|
+
name=lc.name,
|
|
82
|
+
host=lc.host,
|
|
83
|
+
port=lc.port,
|
|
84
|
+
password=password,
|
|
85
|
+
trust=lc.trust,
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
return links
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# -----------------------------------------------------------------------
|
|
92
|
+
# IPC helpers
|
|
93
|
+
# -----------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def agent_socket_path(nick: str) -> str:
|
|
97
|
+
return os.path.join(
|
|
98
|
+
os.environ.get("XDG_RUNTIME_DIR", "/tmp"),
|
|
99
|
+
f"culture-{nick}.sock",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def ipc_request(socket_path: str, msg_type: str, **kwargs) -> dict | None:
|
|
104
|
+
"""Send an IPC request via Unix socket and return the response."""
|
|
105
|
+
from culture.clients.claude.ipc import decode_message, encode_message, make_request
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
reader, writer = await asyncio.wait_for(
|
|
109
|
+
asyncio.open_unix_connection(socket_path),
|
|
110
|
+
timeout=3.0,
|
|
111
|
+
)
|
|
112
|
+
except (ConnectionRefusedError, FileNotFoundError, OSError):
|
|
113
|
+
return None
|
|
114
|
+
try:
|
|
115
|
+
req = make_request(msg_type, **kwargs)
|
|
116
|
+
writer.write(encode_message(req))
|
|
117
|
+
await writer.drain()
|
|
118
|
+
deadline = asyncio.get_event_loop().time() + 3.0
|
|
119
|
+
while True:
|
|
120
|
+
remaining = deadline - asyncio.get_event_loop().time()
|
|
121
|
+
if remaining <= 0:
|
|
122
|
+
return None
|
|
123
|
+
data = await asyncio.wait_for(reader.readline(), timeout=remaining)
|
|
124
|
+
msg = decode_message(data)
|
|
125
|
+
if msg and msg.get("type") == "response":
|
|
126
|
+
return msg
|
|
127
|
+
except (asyncio.TimeoutError, ConnectionError, BrokenPipeError, OSError):
|
|
128
|
+
return None
|
|
129
|
+
finally:
|
|
130
|
+
writer.close()
|
|
131
|
+
try:
|
|
132
|
+
await writer.wait_closed()
|
|
133
|
+
except (ConnectionError, BrokenPipeError, OSError):
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def ipc_shutdown(socket_path: str) -> bool:
|
|
138
|
+
"""Send a shutdown command via Unix socket IPC."""
|
|
139
|
+
resp = await ipc_request(socket_path, "shutdown")
|
|
140
|
+
return resp is not None and resp.get("ok", False)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# -----------------------------------------------------------------------
|
|
144
|
+
# Agent stop helpers (used by agent.py and mesh.py)
|
|
145
|
+
# -----------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def stop_agent(nick: str) -> None:
|
|
149
|
+
"""Stop a single agent by trying IPC shutdown first, then PID file."""
|
|
150
|
+
socket_path = os.path.join(
|
|
151
|
+
os.environ.get("XDG_RUNTIME_DIR", "/tmp"),
|
|
152
|
+
f"culture-{nick}.sock",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if _try_ipc_shutdown(nick, socket_path):
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
_try_pid_shutdown(nick)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _try_ipc_shutdown(nick: str, socket_path: str) -> bool:
|
|
162
|
+
"""Attempt graceful IPC shutdown. Return True if the agent stopped."""
|
|
163
|
+
if not os.path.exists(socket_path):
|
|
164
|
+
return False
|
|
165
|
+
try:
|
|
166
|
+
success = asyncio.run(ipc_shutdown(socket_path))
|
|
167
|
+
if not success:
|
|
168
|
+
return False
|
|
169
|
+
except Exception:
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
print(f"Agent '{nick}' shutdown requested via IPC")
|
|
173
|
+
pid_name = f"agent-{nick}"
|
|
174
|
+
pid = read_pid(pid_name)
|
|
175
|
+
if not pid:
|
|
176
|
+
print(f"Agent '{nick}' stopped")
|
|
177
|
+
return True
|
|
178
|
+
for _ in range(50):
|
|
179
|
+
if not is_process_alive(pid):
|
|
180
|
+
remove_pid(pid_name)
|
|
181
|
+
print(f"Agent '{nick}' stopped")
|
|
182
|
+
return True
|
|
183
|
+
time.sleep(0.1)
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _try_pid_shutdown(nick: str) -> None:
|
|
188
|
+
"""Stop an agent via PID file with SIGTERM/SIGKILL fallback."""
|
|
189
|
+
pid_name = f"agent-{nick}"
|
|
190
|
+
pid = read_pid(pid_name)
|
|
191
|
+
|
|
192
|
+
if pid is None:
|
|
193
|
+
print(f"No PID file for agent '{nick}'")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
if pid <= 0:
|
|
197
|
+
print(f"Invalid PID {pid} for agent '{nick}' — removing corrupt PID file")
|
|
198
|
+
remove_pid(pid_name)
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
if not is_process_alive(pid):
|
|
202
|
+
print(f"Agent '{nick}' is not running (stale PID {pid})")
|
|
203
|
+
remove_pid(pid_name)
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
if not is_culture_process(pid):
|
|
207
|
+
print(f"PID {pid} is not a culture process — removing stale PID file")
|
|
208
|
+
remove_pid(pid_name)
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
print(f"Stopping agent '{nick}' (PID {pid})...")
|
|
212
|
+
try:
|
|
213
|
+
os.kill(pid, signal.SIGTERM)
|
|
214
|
+
except ProcessLookupError:
|
|
215
|
+
remove_pid(pid_name)
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
for _ in range(50):
|
|
219
|
+
if not is_process_alive(pid):
|
|
220
|
+
print(f"Agent '{nick}' stopped")
|
|
221
|
+
remove_pid(pid_name)
|
|
222
|
+
return
|
|
223
|
+
time.sleep(0.1)
|
|
224
|
+
|
|
225
|
+
if not is_culture_process(pid):
|
|
226
|
+
print(f"PID {pid} is no longer a culture process — aborting kill")
|
|
227
|
+
remove_pid(pid_name)
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
if sys.platform == "win32":
|
|
231
|
+
print(f"Agent '{nick}' did not stop gracefully, terminating")
|
|
232
|
+
sig = signal.SIGTERM
|
|
233
|
+
else:
|
|
234
|
+
print(f"Agent '{nick}' did not stop gracefully, sending SIGKILL")
|
|
235
|
+
sig = signal.SIGKILL
|
|
236
|
+
try:
|
|
237
|
+
os.kill(pid, sig)
|
|
238
|
+
except ProcessLookupError:
|
|
239
|
+
pass
|
|
240
|
+
remove_pid(pid_name)
|
|
241
|
+
print(f"Agent '{nick}' killed")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# -----------------------------------------------------------------------
|
|
245
|
+
# Server stop helper (used by mesh.py)
|
|
246
|
+
# -----------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def server_stop_by_name(name: str) -> None:
|
|
250
|
+
"""Stop a server by name (helper for setup --uninstall and update)."""
|
|
251
|
+
pid_name = f"server-{name}"
|
|
252
|
+
pid = read_pid(pid_name)
|
|
253
|
+
if not pid or not is_process_alive(pid):
|
|
254
|
+
if pid:
|
|
255
|
+
remove_pid(pid_name)
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
if not is_culture_process(pid):
|
|
259
|
+
remove_pid(pid_name)
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
os.kill(pid, signal.SIGTERM)
|
|
263
|
+
for _ in range(50):
|
|
264
|
+
if not is_process_alive(pid):
|
|
265
|
+
remove_pid(pid_name)
|
|
266
|
+
return
|
|
267
|
+
time.sleep(0.1)
|
|
268
|
+
|
|
269
|
+
if sys.platform == "win32":
|
|
270
|
+
try:
|
|
271
|
+
os.kill(pid, signal.SIGTERM)
|
|
272
|
+
except ProcessLookupError:
|
|
273
|
+
pass
|
|
274
|
+
else:
|
|
275
|
+
try:
|
|
276
|
+
os.kill(pid, signal.SIGKILL)
|
|
277
|
+
except ProcessLookupError:
|
|
278
|
+
pass
|
|
279
|
+
remove_pid(pid_name)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# -----------------------------------------------------------------------
|
|
283
|
+
# Mesh config helpers (used by server.py and mesh.py)
|
|
284
|
+
# -----------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def generate_mesh_from_agents(mesh_config_path: str):
|
|
288
|
+
"""Fall back to generating mesh.yaml from agents.yaml when mesh.yaml is missing."""
|
|
289
|
+
from culture.mesh_config import from_daemon_config, save_mesh_config
|
|
290
|
+
|
|
291
|
+
if not os.path.isfile(DEFAULT_CONFIG):
|
|
292
|
+
print(f"Mesh config not found: {mesh_config_path}", file=sys.stderr)
|
|
293
|
+
print(f"Agent config not found either: {DEFAULT_CONFIG}", file=sys.stderr)
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
daemon_config = load_config(DEFAULT_CONFIG)
|
|
297
|
+
mesh = from_daemon_config(daemon_config)
|
|
298
|
+
save_mesh_config(mesh, mesh_config_path)
|
|
299
|
+
print(f"No mesh.yaml found — generated from {DEFAULT_CONFIG}")
|
|
300
|
+
return mesh
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def build_server_start_cmd(mesh, culture_bin: str, mesh_config_path: str) -> list[str]:
|
|
304
|
+
"""Build the server start command with --foreground and --mesh-config."""
|
|
305
|
+
return [
|
|
306
|
+
culture_bin,
|
|
307
|
+
"server",
|
|
308
|
+
"start",
|
|
309
|
+
"--foreground",
|
|
310
|
+
"--name",
|
|
311
|
+
mesh.server.name,
|
|
312
|
+
"--host",
|
|
313
|
+
mesh.server.host,
|
|
314
|
+
"--port",
|
|
315
|
+
str(mesh.server.port),
|
|
316
|
+
"--mesh-config",
|
|
317
|
+
mesh_config_path,
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# -----------------------------------------------------------------------
|
|
322
|
+
# Observer helper (used by channel.py and agent.py)
|
|
323
|
+
# -----------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def get_observer(config_path: str):
|
|
327
|
+
"""Create an IRCObserver from the config file."""
|
|
328
|
+
from culture.observer import IRCObserver
|
|
329
|
+
|
|
330
|
+
config = load_config_or_default(config_path)
|
|
331
|
+
return IRCObserver(
|
|
332
|
+
host=config.server.host,
|
|
333
|
+
port=config.server.port,
|
|
334
|
+
server_name=config.server.name,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# -----------------------------------------------------------------------
|
|
339
|
+
# Agent status display helpers
|
|
340
|
+
# -----------------------------------------------------------------------
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def agent_process_status(agent) -> tuple[str, int | None]:
|
|
344
|
+
"""Return (status_str, pid_or_none) for an agent."""
|
|
345
|
+
pid_name = f"agent-{agent.nick}"
|
|
346
|
+
pid = read_pid(pid_name)
|
|
347
|
+
if pid and is_process_alive(pid):
|
|
348
|
+
socket_path = agent_socket_path(agent.nick)
|
|
349
|
+
if os.path.exists(socket_path):
|
|
350
|
+
return "running", pid
|
|
351
|
+
return "starting", pid
|
|
352
|
+
if pid:
|
|
353
|
+
remove_pid(pid_name)
|
|
354
|
+
return "stopped", None
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def print_agent_detail(agent, config_path: str, args: argparse.Namespace) -> None:
|
|
358
|
+
"""Print detailed status for a single agent, including live IPC activity query."""
|
|
359
|
+
status, pid = agent_process_status(agent)
|
|
360
|
+
print(agent.nick)
|
|
361
|
+
print(f" Status: {status}")
|
|
362
|
+
print(f" PID: {pid or '-'}")
|
|
363
|
+
|
|
364
|
+
if status == "running":
|
|
365
|
+
resp = asyncio.run(ipc_request(agent_socket_path(agent.nick), "status", query=True))
|
|
366
|
+
if resp and resp.get("ok"):
|
|
367
|
+
data = resp.get("data", {})
|
|
368
|
+
print(f" Activity: {data.get('description', 'nothing')}")
|
|
369
|
+
print(f" Turns: {data.get('turn_count', 0)}")
|
|
370
|
+
print(f" Paused: {'yes' if data.get('paused') else 'no'}")
|
|
371
|
+
else:
|
|
372
|
+
print(" Activity: unknown (daemon may need restart)")
|
|
373
|
+
else:
|
|
374
|
+
print(" Activity: -")
|
|
375
|
+
|
|
376
|
+
channels = agent.channels if isinstance(agent.channels, list) else []
|
|
377
|
+
print(f" Directory: {agent.directory}")
|
|
378
|
+
print(f" Backend: {agent.agent}")
|
|
379
|
+
print(f" Channels: {', '.join(channels)}")
|
|
380
|
+
print(f" Model: {agent.model}")
|
|
381
|
+
print(f" Config: {config_path}")
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def print_agents_overview(agents: list, show_activity: bool) -> None:
|
|
385
|
+
"""Print a table of all agents with status, PID, and optionally activity."""
|
|
386
|
+
if show_activity:
|
|
387
|
+
print(f"{'NICK':<30} {'STATUS':<12} {'PID':<10} {'ACTIVITY'}")
|
|
388
|
+
print("-" * 72)
|
|
389
|
+
else:
|
|
390
|
+
print(f"{'NICK':<30} {'STATUS':<12} {'PID':<10}")
|
|
391
|
+
print("-" * 52)
|
|
392
|
+
|
|
393
|
+
for agent in agents:
|
|
394
|
+
status, pid = agent_process_status(agent)
|
|
395
|
+
activity = "-"
|
|
396
|
+
|
|
397
|
+
if show_activity and status == "running":
|
|
398
|
+
resp = asyncio.run(ipc_request(agent_socket_path(agent.nick), "status"))
|
|
399
|
+
if resp and resp.get("ok"):
|
|
400
|
+
activity = resp.get("data", {}).get("description", "nothing")
|
|
401
|
+
|
|
402
|
+
if show_activity:
|
|
403
|
+
print(f"{agent.nick:<30} {status:<12} {str(pid or '-'):<10} {activity}")
|
|
404
|
+
else:
|
|
405
|
+
print(f"{agent.nick:<30} {status:<12} {str(pid or '-'):<10}")
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def print_bot_listing() -> None:
|
|
409
|
+
"""Print a table of configured bots (if any exist)."""
|
|
410
|
+
from culture.bots.config import BOTS_DIR, load_bot_config
|
|
411
|
+
|
|
412
|
+
if BOTS_DIR.is_dir():
|
|
413
|
+
bot_configs = []
|
|
414
|
+
for bot_dir in sorted(BOTS_DIR.iterdir()):
|
|
415
|
+
yaml_path = bot_dir / "bot.yaml"
|
|
416
|
+
if yaml_path.is_file():
|
|
417
|
+
try:
|
|
418
|
+
bot_configs.append(load_bot_config(yaml_path))
|
|
419
|
+
except Exception:
|
|
420
|
+
pass
|
|
421
|
+
if bot_configs:
|
|
422
|
+
print()
|
|
423
|
+
print(f"{'BOT':<30} {'TRIGGER':<12} {'CHANNELS'}")
|
|
424
|
+
print("-" * 60)
|
|
425
|
+
for bc in bot_configs:
|
|
426
|
+
channels = ", ".join(bc.channels) if bc.channels else "-"
|
|
427
|
+
print(f"{bc.name:<30} {bc.trigger_type:<12} {channels}")
|