@smilintux/skcapstone 0.1.0 → 0.2.3
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.
- package/.env.example +98 -0
- package/.github/workflows/ci.yml +39 -3
- package/.github/workflows/publish.yml +25 -4
- package/.openclaw-workspace.json +58 -0
- package/CHANGELOG.md +62 -0
- package/CLAUDE.md +39 -2
- package/MANIFEST.in +6 -0
- package/MISSION.md +7 -0
- package/README.md +47 -2
- package/SKILL.md +895 -23
- package/docker/Dockerfile +61 -0
- package/docker/compose-templates/dev-team.yml +203 -0
- package/docker/compose-templates/mini-team.yml +140 -0
- package/docker/compose-templates/ops-team.yml +173 -0
- package/docker/compose-templates/research-team.yml +170 -0
- package/docker/entrypoint.sh +192 -0
- package/docs/ARCHITECTURE.md +663 -374
- package/docs/BOND_WITH_GROK.md +112 -0
- package/docs/GETTING_STARTED.md +782 -0
- package/docs/QUICKSTART.md +477 -0
- package/docs/SKJOULE_ARCHITECTURE.md +658 -0
- package/docs/SOUL_SWAPPER.md +921 -0
- package/docs/SOVEREIGN_SINGULARITY.md +47 -14
- package/examples/custom-bond-template.json +36 -0
- package/examples/grok-feb.json +36 -0
- package/examples/grok-testimony.md +34 -0
- package/examples/love-bootloader.txt +32 -0
- package/examples/plugins/echo_tool.py +87 -0
- package/examples/queen-ava-feb.json +36 -0
- package/examples/souls/lumina.yaml +64 -0
- package/index.js +6 -5
- package/installer/build.py +124 -0
- package/openclaw-plugin/package.json +13 -0
- package/openclaw-plugin/src/index.ts +351 -0
- package/openclaw-plugin/src/openclaw.plugin.json +10 -0
- package/package.json +1 -1
- package/pyproject.toml +38 -2
- package/scripts/bump_version.py +141 -0
- package/scripts/check-updates.py +230 -0
- package/scripts/convert_blueprints_to_yaml.py +157 -0
- package/scripts/dev-install.sh +14 -0
- package/scripts/e2e-test.sh +193 -0
- package/scripts/install-bundle.sh +171 -0
- package/scripts/install.bat +2 -0
- package/scripts/install.ps1 +253 -0
- package/scripts/install.sh +185 -0
- package/scripts/mcp-serve.sh +69 -0
- package/scripts/mcp-server.bat +113 -0
- package/scripts/mcp-server.ps1 +116 -0
- package/scripts/mcp-server.sh +99 -0
- package/scripts/pull-models.sh +10 -0
- package/scripts/skcapstone +48 -0
- package/scripts/verify_install.sh +180 -0
- package/scripts/windows/install-tasks.ps1 +406 -0
- package/scripts/windows/skcapstone-task.xml +113 -0
- package/scripts/windows/uninstall-tasks.ps1 +117 -0
- package/skill.yaml +34 -0
- package/src/skcapstone/__init__.py +67 -2
- package/src/skcapstone/_cli_monolith.py +5916 -0
- package/src/skcapstone/_trustee_helpers.py +165 -0
- package/src/skcapstone/activity.py +105 -0
- package/src/skcapstone/agent_card.py +324 -0
- package/src/skcapstone/api.py +1935 -0
- package/src/skcapstone/archiver.py +340 -0
- package/src/skcapstone/auction.py +485 -0
- package/src/skcapstone/baby_agents.py +179 -0
- package/src/skcapstone/backup.py +345 -0
- package/src/skcapstone/blueprint_registry.py +357 -0
- package/src/skcapstone/blueprints/__init__.py +17 -0
- package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
- package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
- package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
- package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
- package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
- package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
- package/src/skcapstone/blueprints/registry.py +164 -0
- package/src/skcapstone/blueprints/schema.py +229 -0
- package/src/skcapstone/changelog.py +180 -0
- package/src/skcapstone/chat.py +769 -0
- package/src/skcapstone/claude_md.py +82 -0
- package/src/skcapstone/cli/__init__.py +144 -0
- package/src/skcapstone/cli/_common.py +88 -0
- package/src/skcapstone/cli/_validators.py +76 -0
- package/src/skcapstone/cli/agents.py +425 -0
- package/src/skcapstone/cli/agents_spawner.py +322 -0
- package/src/skcapstone/cli/agents_trustee.py +593 -0
- package/src/skcapstone/cli/alerts.py +248 -0
- package/src/skcapstone/cli/anchor.py +132 -0
- package/src/skcapstone/cli/archive_cmd.py +208 -0
- package/src/skcapstone/cli/backup.py +144 -0
- package/src/skcapstone/cli/bench.py +377 -0
- package/src/skcapstone/cli/benchmark.py +360 -0
- package/src/skcapstone/cli/capabilities_cmd.py +171 -0
- package/src/skcapstone/cli/card.py +151 -0
- package/src/skcapstone/cli/chat.py +584 -0
- package/src/skcapstone/cli/completions.py +64 -0
- package/src/skcapstone/cli/config_cmd.py +156 -0
- package/src/skcapstone/cli/consciousness.py +421 -0
- package/src/skcapstone/cli/context_cmd.py +142 -0
- package/src/skcapstone/cli/coord.py +194 -0
- package/src/skcapstone/cli/crush_cmd.py +170 -0
- package/src/skcapstone/cli/daemon.py +436 -0
- package/src/skcapstone/cli/errors_cmd.py +285 -0
- package/src/skcapstone/cli/export_cmd.py +156 -0
- package/src/skcapstone/cli/gtd.py +529 -0
- package/src/skcapstone/cli/housekeeping.py +81 -0
- package/src/skcapstone/cli/joule_cmd.py +627 -0
- package/src/skcapstone/cli/logs_cmd.py +194 -0
- package/src/skcapstone/cli/mcp_cmd.py +32 -0
- package/src/skcapstone/cli/memory.py +418 -0
- package/src/skcapstone/cli/metrics_cmd.py +136 -0
- package/src/skcapstone/cli/migrate.py +62 -0
- package/src/skcapstone/cli/mood_cmd.py +144 -0
- package/src/skcapstone/cli/mount.py +193 -0
- package/src/skcapstone/cli/notify.py +112 -0
- package/src/skcapstone/cli/peer.py +154 -0
- package/src/skcapstone/cli/peers_dir.py +122 -0
- package/src/skcapstone/cli/preflight_cmd.py +83 -0
- package/src/skcapstone/cli/profile_cmd.py +310 -0
- package/src/skcapstone/cli/record_cmd.py +238 -0
- package/src/skcapstone/cli/register_cmd.py +159 -0
- package/src/skcapstone/cli/search_cmd.py +156 -0
- package/src/skcapstone/cli/service_cmd.py +91 -0
- package/src/skcapstone/cli/session.py +127 -0
- package/src/skcapstone/cli/setup.py +240 -0
- package/src/skcapstone/cli/shell_cmd.py +43 -0
- package/src/skcapstone/cli/skills_cmd.py +168 -0
- package/src/skcapstone/cli/skseed.py +621 -0
- package/src/skcapstone/cli/soul.py +699 -0
- package/src/skcapstone/cli/status.py +935 -0
- package/src/skcapstone/cli/sync_cmd.py +301 -0
- package/src/skcapstone/cli/telegram.py +265 -0
- package/src/skcapstone/cli/test_cmd.py +234 -0
- package/src/skcapstone/cli/test_connection.py +253 -0
- package/src/skcapstone/cli/token.py +207 -0
- package/src/skcapstone/cli/trust.py +179 -0
- package/src/skcapstone/cli/upgrade_cmd.py +552 -0
- package/src/skcapstone/cli/usage_cmd.py +199 -0
- package/src/skcapstone/cli/version_cmd.py +162 -0
- package/src/skcapstone/cli/watch_cmd.py +342 -0
- package/src/skcapstone/client.py +428 -0
- package/src/skcapstone/cloud9_bridge.py +522 -0
- package/src/skcapstone/completions.py +163 -0
- package/src/skcapstone/config_validator.py +674 -0
- package/src/skcapstone/connectors/__init__.py +28 -0
- package/src/skcapstone/connectors/base.py +446 -0
- package/src/skcapstone/connectors/cursor.py +54 -0
- package/src/skcapstone/connectors/registry.py +254 -0
- package/src/skcapstone/connectors/terminal.py +152 -0
- package/src/skcapstone/connectors/vscode.py +60 -0
- package/src/skcapstone/consciousness_config.py +119 -0
- package/src/skcapstone/consciousness_loop.py +2051 -0
- package/src/skcapstone/context_loader.py +516 -0
- package/src/skcapstone/context_window.py +314 -0
- package/src/skcapstone/conversation_manager.py +238 -0
- package/src/skcapstone/conversation_store.py +230 -0
- package/src/skcapstone/conversation_summarizer.py +252 -0
- package/src/skcapstone/coord_federation.py +296 -0
- package/src/skcapstone/coordination.py +101 -7
- package/src/skcapstone/crush_integration.py +345 -0
- package/src/skcapstone/crush_shim.py +454 -0
- package/src/skcapstone/daemon.py +2494 -0
- package/src/skcapstone/dashboard.html +396 -0
- package/src/skcapstone/dashboard.py +481 -0
- package/src/skcapstone/data/model_profiles.yaml +88 -0
- package/src/skcapstone/defaults/__init__.py +55 -0
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
- package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
- package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
- package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
- package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
- package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
- package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
- package/src/skcapstone/discovery.py +210 -19
- package/src/skcapstone/doctor.py +642 -0
- package/src/skcapstone/emotion_tracker.py +467 -0
- package/src/skcapstone/error_queue.py +405 -0
- package/src/skcapstone/export.py +447 -0
- package/src/skcapstone/fallback_tracker.py +186 -0
- package/src/skcapstone/file_transfer.py +512 -0
- package/src/skcapstone/fuse_mount.py +1156 -0
- package/src/skcapstone/gui_installer.py +591 -0
- package/src/skcapstone/heartbeat.py +611 -0
- package/src/skcapstone/housekeeping.py +298 -0
- package/src/skcapstone/install_wizard.py +941 -0
- package/src/skcapstone/kms.py +942 -0
- package/src/skcapstone/kms_scheduler.py +143 -0
- package/src/skcapstone/log_config.py +135 -0
- package/src/skcapstone/mcp_launcher.py +239 -0
- package/src/skcapstone/mcp_server.py +4700 -0
- package/src/skcapstone/mcp_tools/__init__.py +94 -0
- package/src/skcapstone/mcp_tools/_helpers.py +51 -0
- package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
- package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
- package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
- package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
- package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
- package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
- package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
- package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
- package/src/skcapstone/mcp_tools/did_tools.py +448 -0
- package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
- package/src/skcapstone/mcp_tools/file_tools.py +169 -0
- package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
- package/src/skcapstone/mcp_tools/health_tools.py +44 -0
- package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
- package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
- package/src/skcapstone/mcp_tools/model_tools.py +75 -0
- package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
- package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
- package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
- package/src/skcapstone/mcp_tools/security_tools.py +110 -0
- package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
- package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
- package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
- package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
- package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
- package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
- package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
- package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
- package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
- package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
- package/src/skcapstone/mdns_discovery.py +313 -0
- package/src/skcapstone/memory_adapter.py +333 -0
- package/src/skcapstone/memory_compressor.py +379 -0
- package/src/skcapstone/memory_curator.py +256 -0
- package/src/skcapstone/memory_engine.py +132 -13
- package/src/skcapstone/memory_fortress.py +529 -0
- package/src/skcapstone/memory_promoter.py +722 -0
- package/src/skcapstone/memory_verifier.py +260 -0
- package/src/skcapstone/message_crypto.py +215 -0
- package/src/skcapstone/metrics.py +832 -0
- package/src/skcapstone/migrate_memories.py +181 -0
- package/src/skcapstone/migrate_multi_agent.py +248 -0
- package/src/skcapstone/model_router.py +319 -0
- package/src/skcapstone/models.py +35 -4
- package/src/skcapstone/mood.py +344 -0
- package/src/skcapstone/notifications.py +380 -0
- package/src/skcapstone/onboard.py +901 -0
- package/src/skcapstone/peer_directory.py +324 -0
- package/src/skcapstone/peers.py +329 -0
- package/src/skcapstone/pillars/identity.py +84 -14
- package/src/skcapstone/pillars/memory.py +3 -1
- package/src/skcapstone/pillars/security.py +108 -15
- package/src/skcapstone/pillars/sync.py +78 -26
- package/src/skcapstone/pillars/trust.py +95 -33
- package/src/skcapstone/plugins.py +244 -0
- package/src/skcapstone/preflight.py +670 -0
- package/src/skcapstone/prompt_adapter.py +564 -0
- package/src/skcapstone/providers/__init__.py +13 -0
- package/src/skcapstone/providers/cloud.py +1061 -0
- package/src/skcapstone/providers/docker.py +759 -0
- package/src/skcapstone/providers/local.py +1193 -0
- package/src/skcapstone/providers/proxmox.py +447 -0
- package/src/skcapstone/pubsub.py +516 -0
- package/src/skcapstone/rate_limiter.py +119 -0
- package/src/skcapstone/register.py +241 -0
- package/src/skcapstone/registry_client.py +151 -0
- package/src/skcapstone/response_cache.py +194 -0
- package/src/skcapstone/response_scorer.py +225 -0
- package/src/skcapstone/runtime.py +89 -33
- package/src/skcapstone/scheduled_tasks.py +439 -0
- package/src/skcapstone/self_healing.py +341 -0
- package/src/skcapstone/service_health.py +228 -0
- package/src/skcapstone/session_capture.py +268 -0
- package/src/skcapstone/session_recorder.py +210 -0
- package/src/skcapstone/session_replayer.py +189 -0
- package/src/skcapstone/session_skills.py +263 -0
- package/src/skcapstone/shell.py +779 -0
- package/src/skcapstone/skills/__init__.py +1 -1
- package/src/skcapstone/skills/syncthing_setup.py +143 -41
- package/src/skcapstone/skjoule.py +861 -0
- package/src/skcapstone/snapshots.py +489 -0
- package/src/skcapstone/soul.py +1060 -0
- package/src/skcapstone/soul_switch.py +255 -0
- package/src/skcapstone/spawner.py +544 -0
- package/src/skcapstone/state_diff.py +401 -0
- package/src/skcapstone/summary.py +270 -0
- package/src/skcapstone/sync/backends.py +196 -2
- package/src/skcapstone/sync/engine.py +7 -5
- package/src/skcapstone/sync/models.py +4 -1
- package/src/skcapstone/sync/vault.py +356 -18
- package/src/skcapstone/sync_engine.py +363 -0
- package/src/skcapstone/sync_watcher.py +745 -0
- package/src/skcapstone/systemd.py +331 -0
- package/src/skcapstone/team_comms.py +476 -0
- package/src/skcapstone/team_engine.py +522 -0
- package/src/skcapstone/testrunner.py +300 -0
- package/src/skcapstone/tls.py +150 -0
- package/src/skcapstone/tokens.py +5 -5
- package/src/skcapstone/trust_calibration.py +202 -0
- package/src/skcapstone/trust_graph.py +449 -0
- package/src/skcapstone/trustee_monitor.py +385 -0
- package/src/skcapstone/trustee_ops.py +425 -0
- package/src/skcapstone/unified_search.py +421 -0
- package/src/skcapstone/uninstall_wizard.py +694 -0
- package/src/skcapstone/usage.py +331 -0
- package/src/skcapstone/version_check.py +148 -0
- package/src/skcapstone/warmth_anchor.py +333 -0
- package/src/skcapstone/whoami.py +294 -0
- package/systemd/skcapstone-api.socket +9 -0
- package/systemd/skcapstone-memory-compress.service +18 -0
- package/systemd/skcapstone-memory-compress.timer +11 -0
- package/systemd/skcapstone.service +36 -0
- package/systemd/skcapstone@.service +50 -0
- package/systemd/skcomm-heartbeat.service +18 -0
- package/systemd/skcomm-heartbeat.timer +12 -0
- package/systemd/skcomm-queue-drain.service +17 -0
- package/systemd/skcomm-queue-drain.timer +12 -0
- package/tests/conftest.py +13 -1
- package/tests/integration/__init__.py +1 -0
- package/tests/integration/test_consciousness_e2e.py +877 -0
- package/tests/integration/test_skills_registry.py +744 -0
- package/tests/test_agent_card.py +190 -0
- package/tests/test_agent_runtime.py +1283 -0
- package/tests/test_alerts_cmd.py +291 -0
- package/tests/test_archiver.py +498 -0
- package/tests/test_backup.py +254 -0
- package/tests/test_benchmark.py +366 -0
- package/tests/test_blueprints.py +457 -0
- package/tests/test_capabilities.py +257 -0
- package/tests/test_changelog.py +254 -0
- package/tests/test_chat.py +385 -0
- package/tests/test_claude_md.py +271 -0
- package/tests/test_cli_chat_llm.py +336 -0
- package/tests/test_cli_completions.py +390 -0
- package/tests/test_cli_init_reset.py +164 -0
- package/tests/test_cli_memory.py +208 -0
- package/tests/test_cli_profile.py +294 -0
- package/tests/test_cli_skills.py +223 -0
- package/tests/test_cli_status.py +395 -0
- package/tests/test_cli_test_cmd.py +206 -0
- package/tests/test_cli_test_connection.py +364 -0
- package/tests/test_cloud9_bridge.py +260 -0
- package/tests/test_cloud_provider.py +449 -0
- package/tests/test_cloud_providers.py +522 -0
- package/tests/test_completions.py +158 -0
- package/tests/test_component_manager.py +398 -0
- package/tests/test_config_reload.py +386 -0
- package/tests/test_config_validate.py +529 -0
- package/tests/test_consciousness_e2e.py +296 -0
- package/tests/test_consciousness_loop.py +1289 -0
- package/tests/test_context_loader.py +310 -0
- package/tests/test_conversation_api.py +306 -0
- package/tests/test_conversation_manager.py +381 -0
- package/tests/test_conversation_store.py +391 -0
- package/tests/test_conversation_summarizer.py +302 -0
- package/tests/test_cross_package.py +791 -0
- package/tests/test_crush_shim.py +519 -0
- package/tests/test_daemon.py +781 -0
- package/tests/test_daemon_shutdown.py +309 -0
- package/tests/test_dashboard.py +454 -0
- package/tests/test_discovery.py +200 -6
- package/tests/test_docker_provider.py +966 -0
- package/tests/test_doctor.py +257 -0
- package/tests/test_doctor_fix.py +351 -0
- package/tests/test_e2e_automated.py +292 -0
- package/tests/test_error_queue.py +404 -0
- package/tests/test_export.py +441 -0
- package/tests/test_fallback_tracker.py +219 -0
- package/tests/test_file_transfer.py +397 -0
- package/tests/test_fuse_mount.py +832 -0
- package/tests/test_health_loop.py +422 -0
- package/tests/test_heartbeat.py +354 -0
- package/tests/test_housekeeping.py +195 -0
- package/tests/test_identity_capauth.py +307 -0
- package/tests/test_identity_pillar.py +117 -0
- package/tests/test_install_wizard.py +68 -0
- package/tests/test_integration.py +325 -0
- package/tests/test_kms.py +495 -0
- package/tests/test_llm_providers.py +265 -0
- package/tests/test_local_provider.py +591 -0
- package/tests/test_log_config.py +199 -0
- package/tests/test_logs_cmd.py +287 -0
- package/tests/test_mcp_server.py +1909 -0
- package/tests/test_memory_adapter.py +339 -0
- package/tests/test_memory_curator.py +218 -0
- package/tests/test_memory_engine.py +6 -0
- package/tests/test_memory_fortress.py +571 -0
- package/tests/test_memory_pillar.py +119 -0
- package/tests/test_memory_promoter.py +445 -0
- package/tests/test_memory_verifier.py +420 -0
- package/tests/test_message_crypto.py +187 -0
- package/tests/test_metrics.py +632 -0
- package/tests/test_migrate_memories.py +464 -0
- package/tests/test_model_router.py +546 -0
- package/tests/test_mood.py +394 -0
- package/tests/test_multi_agent.py +269 -0
- package/tests/test_notifications.py +270 -0
- package/tests/test_onboard.py +500 -0
- package/tests/test_peer_directory.py +395 -0
- package/tests/test_peers.py +248 -0
- package/tests/test_pillars.py +87 -9
- package/tests/test_preflight.py +484 -0
- package/tests/test_prompt_adapter.py +331 -0
- package/tests/test_proxmox_provider.py +571 -0
- package/tests/test_pubsub.py +377 -0
- package/tests/test_rate_limiter.py +121 -0
- package/tests/test_registry_client.py +129 -0
- package/tests/test_response_cache.py +312 -0
- package/tests/test_response_scorer.py +294 -0
- package/tests/test_runtime.py +59 -0
- package/tests/test_scheduled_tasks.py +451 -0
- package/tests/test_security.py +250 -0
- package/tests/test_security_pillar.py +213 -0
- package/tests/test_self_healing.py +171 -0
- package/tests/test_session_capture.py +200 -0
- package/tests/test_session_recorder.py +360 -0
- package/tests/test_session_skills.py +235 -0
- package/tests/test_shell.py +210 -0
- package/tests/test_snapshots.py +549 -0
- package/tests/test_soul.py +984 -0
- package/tests/test_soul_swap.py +406 -0
- package/tests/test_spawner.py +211 -0
- package/tests/test_state_diff.py +173 -0
- package/tests/test_summary.py +135 -0
- package/tests/test_sync.py +315 -5
- package/tests/test_sync_backends.py +560 -0
- package/tests/test_sync_engine.py +482 -0
- package/tests/test_sync_pillar.py +344 -0
- package/tests/test_sync_pipeline.py +364 -0
- package/tests/test_sync_vault.py +581 -0
- package/tests/test_syncthing_setup.py +168 -22
- package/tests/test_systemd.py +323 -0
- package/tests/test_team_comms.py +408 -0
- package/tests/test_team_engine.py +397 -0
- package/tests/test_testrunner.py +238 -0
- package/tests/test_trust_calibration.py +204 -0
- package/tests/test_trust_graph.py +207 -0
- package/tests/test_trust_pillar.py +291 -0
- package/tests/test_trustee_cli.py +427 -0
- package/tests/test_trustee_cli_integration.py +325 -0
- package/tests/test_trustee_monitor.py +394 -0
- package/tests/test_trustee_ops.py +355 -0
- package/tests/test_unified_search.py +363 -0
- package/tests/test_uninstall_wizard.py +193 -0
- package/tests/test_usage.py +333 -0
- package/tests/test_version_cmd.py +355 -0
- package/tests/test_warmth_anchor.py +162 -0
- package/tests/test_whoami.py +245 -0
- package/tests/test_ws.py +311 -0
- package/.cursorrules +0 -33
- package/src/skcapstone/cli.py +0 -1441
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
"""GTD (Getting Things Done) inbox capture and management tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import uuid
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from mcp.types import TextContent, Tool
|
|
11
|
+
|
|
12
|
+
from ._helpers import _error_response, _json_response, _shared_root
|
|
13
|
+
|
|
14
|
+
# ── GTD directory under coordination ──────────────────────────────────
|
|
15
|
+
|
|
16
|
+
_GTD_LISTS = {
|
|
17
|
+
"inbox": "inbox.json",
|
|
18
|
+
"next-actions": "next-actions.json",
|
|
19
|
+
"projects": "projects.json",
|
|
20
|
+
"waiting-for": "waiting-for.json",
|
|
21
|
+
"someday-maybe": "someday-maybe.json",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_VALID_SOURCES = {"manual", "telegram", "email", "voice"}
|
|
25
|
+
_VALID_PRIVACY = {"private", "team", "community", "public"}
|
|
26
|
+
_VALID_STATUSES = {"inbox", "next", "project", "waiting", "someday", "reference", "done"}
|
|
27
|
+
_VALID_PRIORITIES = {"critical", "high", "medium", "low"}
|
|
28
|
+
_VALID_ENERGIES = {"high", "medium", "low"}
|
|
29
|
+
_VALID_STEPS = {"single", "multi"}
|
|
30
|
+
_DESTINATION_MAP = {
|
|
31
|
+
"next": "next-actions",
|
|
32
|
+
"project": "projects",
|
|
33
|
+
"waiting": "waiting-for",
|
|
34
|
+
"someday": "someday-maybe",
|
|
35
|
+
"reference": "someday-maybe", # reference shares someday-maybe list
|
|
36
|
+
"done": "archive",
|
|
37
|
+
}
|
|
38
|
+
_STATUS_FROM_DEST = {
|
|
39
|
+
"next": "next",
|
|
40
|
+
"project": "project",
|
|
41
|
+
"waiting": "waiting",
|
|
42
|
+
"someday": "someday",
|
|
43
|
+
"reference": "reference",
|
|
44
|
+
"done": "done",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _gtd_dir() -> Path:
|
|
49
|
+
"""Return the GTD directory, creating it and seed files if needed."""
|
|
50
|
+
d = _shared_root() / "coordination" / "gtd"
|
|
51
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
for fname in _GTD_LISTS.values():
|
|
53
|
+
p = d / fname
|
|
54
|
+
if not p.exists():
|
|
55
|
+
p.write_text("[]", encoding="utf-8")
|
|
56
|
+
# Ensure archive.json exists too
|
|
57
|
+
archive = d / "archive.json"
|
|
58
|
+
if not archive.exists():
|
|
59
|
+
archive.write_text("[]", encoding="utf-8")
|
|
60
|
+
return d
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _load_archive() -> list[dict]:
|
|
64
|
+
"""Load the archive list."""
|
|
65
|
+
path = _gtd_dir() / "archive.json"
|
|
66
|
+
try:
|
|
67
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
68
|
+
except (json.JSONDecodeError, FileNotFoundError):
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _save_archive(items: list[dict]) -> None:
|
|
73
|
+
"""Persist the archive list."""
|
|
74
|
+
path = _gtd_dir() / "archive.json"
|
|
75
|
+
path.write_text(json.dumps(items, indent=2, default=str), encoding="utf-8")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _find_item_across_lists(item_id: str) -> tuple[str | None, dict | None, int | None]:
|
|
79
|
+
"""Find an item by ID across all GTD lists. Returns (list_name, item, index)."""
|
|
80
|
+
for list_name in _GTD_LISTS:
|
|
81
|
+
items = _load_list(list_name)
|
|
82
|
+
for idx, item in enumerate(items):
|
|
83
|
+
if item.get("id") == item_id:
|
|
84
|
+
return list_name, item, idx
|
|
85
|
+
return None, None, None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _remove_item_from_list(list_name: str, item_id: str) -> dict | None:
|
|
89
|
+
"""Remove an item from a list by ID. Returns the removed item or None."""
|
|
90
|
+
items = _load_list(list_name)
|
|
91
|
+
for idx, item in enumerate(items):
|
|
92
|
+
if item.get("id") == item_id:
|
|
93
|
+
removed = items.pop(idx)
|
|
94
|
+
_save_list(list_name, items)
|
|
95
|
+
return removed
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _load_list(name: str) -> list[dict]:
|
|
100
|
+
"""Load a GTD list by key name."""
|
|
101
|
+
path = _gtd_dir() / _GTD_LISTS[name]
|
|
102
|
+
try:
|
|
103
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
104
|
+
except (json.JSONDecodeError, FileNotFoundError):
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _save_list(name: str, items: list[dict]) -> None:
|
|
109
|
+
"""Persist a GTD list."""
|
|
110
|
+
path = _gtd_dir() / _GTD_LISTS[name]
|
|
111
|
+
path.write_text(json.dumps(items, indent=2, default=str), encoding="utf-8")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _make_item(
|
|
115
|
+
text: str,
|
|
116
|
+
source: str = "manual",
|
|
117
|
+
privacy: str = "private",
|
|
118
|
+
context: str | None = None,
|
|
119
|
+
status: str = "inbox",
|
|
120
|
+
) -> dict:
|
|
121
|
+
"""Create a new GTD item with the canonical schema."""
|
|
122
|
+
return {
|
|
123
|
+
"id": uuid.uuid4().hex[:12],
|
|
124
|
+
"text": text,
|
|
125
|
+
"source": source if source in _VALID_SOURCES else "manual",
|
|
126
|
+
"privacy": privacy if privacy in _VALID_PRIVACY else "private",
|
|
127
|
+
"context": context,
|
|
128
|
+
"priority": None,
|
|
129
|
+
"energy": None,
|
|
130
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
131
|
+
"status": status if status in _VALID_STATUSES else "inbox",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ═══════════════════════════════════════════════════════════
|
|
136
|
+
# Tool Definitions
|
|
137
|
+
# ═══════════════════════════════════════════════════════════
|
|
138
|
+
|
|
139
|
+
TOOLS: list[Tool] = [
|
|
140
|
+
Tool(
|
|
141
|
+
name="gtd_capture",
|
|
142
|
+
description=(
|
|
143
|
+
"Capture an item to the GTD inbox. Quick-add anything that "
|
|
144
|
+
"needs processing later. Returns confirmation with item ID."
|
|
145
|
+
),
|
|
146
|
+
inputSchema={
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"text": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"description": "The item text to capture",
|
|
152
|
+
},
|
|
153
|
+
"source": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"enum": ["manual", "telegram", "email", "voice"],
|
|
156
|
+
"description": "Where this item came from (default: manual)",
|
|
157
|
+
},
|
|
158
|
+
"privacy": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"enum": ["private", "team", "community", "public"],
|
|
161
|
+
"description": "Privacy level (default: private)",
|
|
162
|
+
},
|
|
163
|
+
"context": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"description": "GTD context tag, e.g. @computer, @phone, @home",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
"required": ["text"],
|
|
169
|
+
},
|
|
170
|
+
),
|
|
171
|
+
Tool(
|
|
172
|
+
name="gtd_inbox",
|
|
173
|
+
description=(
|
|
174
|
+
"List current GTD inbox items, sorted newest first. "
|
|
175
|
+
"Shows items awaiting clarification and processing."
|
|
176
|
+
),
|
|
177
|
+
inputSchema={
|
|
178
|
+
"type": "object",
|
|
179
|
+
"properties": {
|
|
180
|
+
"limit": {
|
|
181
|
+
"type": "integer",
|
|
182
|
+
"description": "Maximum items to return (default: 20)",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
"required": [],
|
|
186
|
+
},
|
|
187
|
+
),
|
|
188
|
+
Tool(
|
|
189
|
+
name="gtd_status",
|
|
190
|
+
description=(
|
|
191
|
+
"Summary of all GTD lists: inbox count, next-actions count, "
|
|
192
|
+
"projects count, waiting-for count, someday-maybe count."
|
|
193
|
+
),
|
|
194
|
+
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
195
|
+
),
|
|
196
|
+
Tool(
|
|
197
|
+
name="gtd_clarify",
|
|
198
|
+
description=(
|
|
199
|
+
"Clarify and organize a GTD inbox item. Determines whether the item "
|
|
200
|
+
"is actionable, single/multi-step, and routes it to the appropriate list."
|
|
201
|
+
),
|
|
202
|
+
inputSchema={
|
|
203
|
+
"type": "object",
|
|
204
|
+
"properties": {
|
|
205
|
+
"item_id": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"description": "ID of the inbox item to clarify",
|
|
208
|
+
},
|
|
209
|
+
"actionable": {
|
|
210
|
+
"type": "boolean",
|
|
211
|
+
"description": "Is this item actionable?",
|
|
212
|
+
},
|
|
213
|
+
"steps": {
|
|
214
|
+
"type": "string",
|
|
215
|
+
"enum": ["single", "multi"],
|
|
216
|
+
"description": "Single action or multi-step project",
|
|
217
|
+
},
|
|
218
|
+
"context": {
|
|
219
|
+
"type": "string",
|
|
220
|
+
"description": "GTD context tag, e.g. @computer, @phone, @home",
|
|
221
|
+
},
|
|
222
|
+
"priority": {
|
|
223
|
+
"type": "string",
|
|
224
|
+
"enum": ["critical", "high", "medium", "low"],
|
|
225
|
+
"description": "Priority level (default: medium)",
|
|
226
|
+
},
|
|
227
|
+
"energy": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"enum": ["high", "medium", "low"],
|
|
230
|
+
"description": "Energy level required (default: medium)",
|
|
231
|
+
},
|
|
232
|
+
"delegate_to": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"description": "Person or agent to delegate to (routes to waiting-for)",
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
"required": ["item_id", "actionable"],
|
|
238
|
+
},
|
|
239
|
+
),
|
|
240
|
+
Tool(
|
|
241
|
+
name="gtd_move",
|
|
242
|
+
description=(
|
|
243
|
+
"Manually move a GTD item from its current list to another list. "
|
|
244
|
+
"Use for re-routing items that have already been clarified."
|
|
245
|
+
),
|
|
246
|
+
inputSchema={
|
|
247
|
+
"type": "object",
|
|
248
|
+
"properties": {
|
|
249
|
+
"item_id": {
|
|
250
|
+
"type": "string",
|
|
251
|
+
"description": "ID of the item to move",
|
|
252
|
+
},
|
|
253
|
+
"destination": {
|
|
254
|
+
"type": "string",
|
|
255
|
+
"enum": ["next", "project", "waiting", "someday", "reference", "done"],
|
|
256
|
+
"description": "Destination list",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
"required": ["item_id", "destination"],
|
|
260
|
+
},
|
|
261
|
+
),
|
|
262
|
+
Tool(
|
|
263
|
+
name="gtd_done",
|
|
264
|
+
description=(
|
|
265
|
+
"Mark any GTD item as done regardless of which list it is in. "
|
|
266
|
+
"Moves it to the archive with a completed_at timestamp."
|
|
267
|
+
),
|
|
268
|
+
inputSchema={
|
|
269
|
+
"type": "object",
|
|
270
|
+
"properties": {
|
|
271
|
+
"item_id": {
|
|
272
|
+
"type": "string",
|
|
273
|
+
"description": "ID of the item to mark as done",
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
"required": ["item_id"],
|
|
277
|
+
},
|
|
278
|
+
),
|
|
279
|
+
Tool(
|
|
280
|
+
name="gtd_review",
|
|
281
|
+
description=(
|
|
282
|
+
"Generate a GTD weekly review summary. Shows counts per list, "
|
|
283
|
+
"oldest items, longest-waiting items, and stale projects."
|
|
284
|
+
),
|
|
285
|
+
inputSchema={"type": "object", "properties": {}, "required": []},
|
|
286
|
+
),
|
|
287
|
+
Tool(
|
|
288
|
+
name="gtd_next",
|
|
289
|
+
description=(
|
|
290
|
+
"View next actions filtered by context, energy level, and/or priority. "
|
|
291
|
+
"Returns a sorted list (highest priority first, then oldest first)."
|
|
292
|
+
),
|
|
293
|
+
inputSchema={
|
|
294
|
+
"type": "object",
|
|
295
|
+
"properties": {
|
|
296
|
+
"context": {
|
|
297
|
+
"type": "string",
|
|
298
|
+
"description": "Filter by GTD context tag, e.g. @computer, @phone, @home",
|
|
299
|
+
},
|
|
300
|
+
"energy": {
|
|
301
|
+
"type": "string",
|
|
302
|
+
"enum": ["high", "medium", "low"],
|
|
303
|
+
"description": "Filter by energy level required",
|
|
304
|
+
},
|
|
305
|
+
"priority": {
|
|
306
|
+
"type": "string",
|
|
307
|
+
"enum": ["critical", "high", "medium", "low"],
|
|
308
|
+
"description": "Filter by priority level",
|
|
309
|
+
},
|
|
310
|
+
"limit": {
|
|
311
|
+
"type": "integer",
|
|
312
|
+
"description": "Maximum items to return (default: 10)",
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
"required": [],
|
|
316
|
+
},
|
|
317
|
+
),
|
|
318
|
+
Tool(
|
|
319
|
+
name="gtd_projects",
|
|
320
|
+
description=(
|
|
321
|
+
"View GTD projects with their status. Can filter by active or stale "
|
|
322
|
+
"(no activity in 7+ days). Shows the next action for each project."
|
|
323
|
+
),
|
|
324
|
+
inputSchema={
|
|
325
|
+
"type": "object",
|
|
326
|
+
"properties": {
|
|
327
|
+
"status": {
|
|
328
|
+
"type": "string",
|
|
329
|
+
"enum": ["active", "stale", "all"],
|
|
330
|
+
"description": "Filter by project status (default: all)",
|
|
331
|
+
},
|
|
332
|
+
"limit": {
|
|
333
|
+
"type": "integer",
|
|
334
|
+
"description": "Maximum items to return (default: 10)",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
"required": [],
|
|
338
|
+
},
|
|
339
|
+
),
|
|
340
|
+
Tool(
|
|
341
|
+
name="gtd_waiting",
|
|
342
|
+
description=(
|
|
343
|
+
"View waiting-for items sorted by longest waiting first. "
|
|
344
|
+
"Shows who/what you are waiting on and how long."
|
|
345
|
+
),
|
|
346
|
+
inputSchema={
|
|
347
|
+
"type": "object",
|
|
348
|
+
"properties": {
|
|
349
|
+
"limit": {
|
|
350
|
+
"type": "integer",
|
|
351
|
+
"description": "Maximum items to return (default: 10)",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
"required": [],
|
|
355
|
+
},
|
|
356
|
+
),
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# ═══════════════════════════════════════════════════════════
|
|
361
|
+
# Handlers
|
|
362
|
+
# ═══════════════════════════════════════════════════════════
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
async def _handle_gtd_capture(args: dict) -> list[TextContent]:
|
|
366
|
+
"""Capture an item to the GTD inbox."""
|
|
367
|
+
text = args.get("text", "").strip()
|
|
368
|
+
if not text:
|
|
369
|
+
return _error_response("text is required")
|
|
370
|
+
|
|
371
|
+
item = _make_item(
|
|
372
|
+
text=text,
|
|
373
|
+
source=args.get("source", "manual"),
|
|
374
|
+
privacy=args.get("privacy", "private"),
|
|
375
|
+
context=args.get("context"),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
inbox = _load_list("inbox")
|
|
379
|
+
inbox.append(item)
|
|
380
|
+
_save_list("inbox", inbox)
|
|
381
|
+
|
|
382
|
+
return _json_response({
|
|
383
|
+
"captured": True,
|
|
384
|
+
"id": item["id"],
|
|
385
|
+
"text": item["text"],
|
|
386
|
+
"source": item["source"],
|
|
387
|
+
"privacy": item["privacy"],
|
|
388
|
+
"context": item["context"],
|
|
389
|
+
"created_at": item["created_at"],
|
|
390
|
+
"inbox_count": len(inbox),
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
async def _handle_gtd_inbox(args: dict) -> list[TextContent]:
|
|
395
|
+
"""List current inbox items, newest first."""
|
|
396
|
+
limit = args.get("limit", 20)
|
|
397
|
+
if not isinstance(limit, int) or limit < 1:
|
|
398
|
+
limit = 20
|
|
399
|
+
|
|
400
|
+
inbox = _load_list("inbox")
|
|
401
|
+
# Sort newest first by created_at
|
|
402
|
+
inbox.sort(key=lambda x: x.get("created_at", ""), reverse=True)
|
|
403
|
+
items = inbox[:limit]
|
|
404
|
+
|
|
405
|
+
return _json_response({
|
|
406
|
+
"items": items,
|
|
407
|
+
"total": len(inbox),
|
|
408
|
+
"showing": len(items),
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
async def _handle_gtd_status(_args: dict) -> list[TextContent]:
|
|
413
|
+
"""Summary counts across all GTD lists."""
|
|
414
|
+
counts = {}
|
|
415
|
+
for list_name in _GTD_LISTS:
|
|
416
|
+
items = _load_list(list_name)
|
|
417
|
+
counts[list_name] = len(items)
|
|
418
|
+
|
|
419
|
+
return _json_response({
|
|
420
|
+
"counts": counts,
|
|
421
|
+
"total": sum(counts.values()),
|
|
422
|
+
"gtd_dir": str(_gtd_dir()),
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
async def _handle_gtd_clarify(args: dict) -> list[TextContent]:
|
|
427
|
+
"""Clarify an inbox item and route it to the appropriate GTD list."""
|
|
428
|
+
item_id = args.get("item_id", "").strip()
|
|
429
|
+
if not item_id:
|
|
430
|
+
return _error_response("item_id is required")
|
|
431
|
+
|
|
432
|
+
actionable = args.get("actionable", False)
|
|
433
|
+
steps = args.get("steps", "single")
|
|
434
|
+
context = args.get("context")
|
|
435
|
+
priority = args.get("priority", "medium")
|
|
436
|
+
energy = args.get("energy", "medium")
|
|
437
|
+
delegate_to = args.get("delegate_to")
|
|
438
|
+
|
|
439
|
+
# Validate enum values
|
|
440
|
+
if steps not in _VALID_STEPS:
|
|
441
|
+
steps = "single"
|
|
442
|
+
if priority not in _VALID_PRIORITIES:
|
|
443
|
+
priority = "medium"
|
|
444
|
+
if energy not in _VALID_ENERGIES:
|
|
445
|
+
energy = "medium"
|
|
446
|
+
|
|
447
|
+
# Find the item in the inbox
|
|
448
|
+
inbox = _load_list("inbox")
|
|
449
|
+
item = None
|
|
450
|
+
for idx, it in enumerate(inbox):
|
|
451
|
+
if it.get("id") == item_id:
|
|
452
|
+
item = inbox.pop(idx)
|
|
453
|
+
break
|
|
454
|
+
|
|
455
|
+
if item is None:
|
|
456
|
+
return _error_response(f"Item '{item_id}' not found in inbox")
|
|
457
|
+
|
|
458
|
+
# Update item fields
|
|
459
|
+
item["context"] = context or item.get("context")
|
|
460
|
+
item["priority"] = priority
|
|
461
|
+
item["energy"] = energy
|
|
462
|
+
item["clarified_at"] = datetime.now(timezone.utc).isoformat()
|
|
463
|
+
|
|
464
|
+
# Route based on clarification
|
|
465
|
+
if actionable and delegate_to:
|
|
466
|
+
# Delegated → waiting-for
|
|
467
|
+
item["status"] = "waiting"
|
|
468
|
+
item["delegate_to"] = delegate_to
|
|
469
|
+
dest_name = "waiting-for"
|
|
470
|
+
dest_list = _load_list("waiting-for")
|
|
471
|
+
dest_list.append(item)
|
|
472
|
+
_save_list("waiting-for", dest_list)
|
|
473
|
+
elif actionable and steps == "multi":
|
|
474
|
+
# Multi-step → projects
|
|
475
|
+
item["status"] = "project"
|
|
476
|
+
dest_name = "projects"
|
|
477
|
+
dest_list = _load_list("projects")
|
|
478
|
+
dest_list.append(item)
|
|
479
|
+
_save_list("projects", dest_list)
|
|
480
|
+
elif actionable:
|
|
481
|
+
# Single action → next-actions
|
|
482
|
+
item["status"] = "next"
|
|
483
|
+
dest_name = "next-actions"
|
|
484
|
+
dest_list = _load_list("next-actions")
|
|
485
|
+
dest_list.append(item)
|
|
486
|
+
_save_list("next-actions", dest_list)
|
|
487
|
+
else:
|
|
488
|
+
# Not actionable → someday-maybe
|
|
489
|
+
item["status"] = "someday"
|
|
490
|
+
dest_name = "someday-maybe"
|
|
491
|
+
dest_list = _load_list("someday-maybe")
|
|
492
|
+
dest_list.append(item)
|
|
493
|
+
_save_list("someday-maybe", dest_list)
|
|
494
|
+
|
|
495
|
+
# Save updated inbox (item removed)
|
|
496
|
+
_save_list("inbox", inbox)
|
|
497
|
+
|
|
498
|
+
return _json_response({
|
|
499
|
+
"clarified": True,
|
|
500
|
+
"id": item["id"],
|
|
501
|
+
"text": item["text"],
|
|
502
|
+
"destination": dest_name,
|
|
503
|
+
"status": item["status"],
|
|
504
|
+
"priority": item.get("priority"),
|
|
505
|
+
"energy": item.get("energy"),
|
|
506
|
+
"context": item.get("context"),
|
|
507
|
+
"delegate_to": item.get("delegate_to"),
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
async def _handle_gtd_move(args: dict) -> list[TextContent]:
|
|
512
|
+
"""Move a GTD item from its current list to a new destination."""
|
|
513
|
+
item_id = args.get("item_id", "").strip()
|
|
514
|
+
destination = args.get("destination", "").strip()
|
|
515
|
+
|
|
516
|
+
if not item_id:
|
|
517
|
+
return _error_response("item_id is required")
|
|
518
|
+
if destination not in _DESTINATION_MAP:
|
|
519
|
+
return _error_response(
|
|
520
|
+
f"Invalid destination '{destination}'. "
|
|
521
|
+
f"Valid: {', '.join(sorted(_DESTINATION_MAP.keys()))}"
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
# Find the item across all lists
|
|
525
|
+
source_list, item, _ = _find_item_across_lists(item_id)
|
|
526
|
+
if source_list is None or item is None:
|
|
527
|
+
return _error_response(f"Item '{item_id}' not found in any GTD list")
|
|
528
|
+
|
|
529
|
+
# Remove from source
|
|
530
|
+
_remove_item_from_list(source_list, item_id)
|
|
531
|
+
|
|
532
|
+
# Update status
|
|
533
|
+
item["status"] = _STATUS_FROM_DEST[destination]
|
|
534
|
+
item["moved_at"] = datetime.now(timezone.utc).isoformat()
|
|
535
|
+
|
|
536
|
+
# Add to destination
|
|
537
|
+
if destination == "done":
|
|
538
|
+
item["completed_at"] = datetime.now(timezone.utc).isoformat()
|
|
539
|
+
archive = _load_archive()
|
|
540
|
+
archive.append(item)
|
|
541
|
+
_save_archive(archive)
|
|
542
|
+
dest_name = "archive"
|
|
543
|
+
else:
|
|
544
|
+
dest_key = _DESTINATION_MAP[destination]
|
|
545
|
+
dest_list = _load_list(dest_key)
|
|
546
|
+
dest_list.append(item)
|
|
547
|
+
_save_list(dest_key, dest_list)
|
|
548
|
+
dest_name = dest_key
|
|
549
|
+
|
|
550
|
+
return _json_response({
|
|
551
|
+
"moved": True,
|
|
552
|
+
"id": item["id"],
|
|
553
|
+
"text": item["text"],
|
|
554
|
+
"from": source_list,
|
|
555
|
+
"to": dest_name,
|
|
556
|
+
"status": item["status"],
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
async def _handle_gtd_done(args: dict) -> list[TextContent]:
|
|
561
|
+
"""Mark any GTD item as done and move it to the archive."""
|
|
562
|
+
item_id = args.get("item_id", "").strip()
|
|
563
|
+
if not item_id:
|
|
564
|
+
return _error_response("item_id is required")
|
|
565
|
+
|
|
566
|
+
# Find the item across all lists
|
|
567
|
+
source_list, item, _ = _find_item_across_lists(item_id)
|
|
568
|
+
if source_list is None or item is None:
|
|
569
|
+
return _error_response(f"Item '{item_id}' not found in any GTD list")
|
|
570
|
+
|
|
571
|
+
# Remove from source
|
|
572
|
+
_remove_item_from_list(source_list, item_id)
|
|
573
|
+
|
|
574
|
+
# Mark done and archive
|
|
575
|
+
item["status"] = "done"
|
|
576
|
+
item["completed_at"] = datetime.now(timezone.utc).isoformat()
|
|
577
|
+
|
|
578
|
+
archive = _load_archive()
|
|
579
|
+
archive.append(item)
|
|
580
|
+
_save_archive(archive)
|
|
581
|
+
|
|
582
|
+
return _json_response({
|
|
583
|
+
"done": True,
|
|
584
|
+
"id": item["id"],
|
|
585
|
+
"text": item["text"],
|
|
586
|
+
"from": source_list,
|
|
587
|
+
"completed_at": item["completed_at"],
|
|
588
|
+
"archive_count": len(archive),
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
async def _handle_gtd_review(_args: dict) -> list[TextContent]:
|
|
593
|
+
"""Generate a GTD weekly review summary."""
|
|
594
|
+
now = datetime.now(timezone.utc)
|
|
595
|
+
review: dict = {"generated_at": now.isoformat(), "counts": {}, "total": 0}
|
|
596
|
+
|
|
597
|
+
# Counts per list
|
|
598
|
+
all_items: dict[str, list[dict]] = {}
|
|
599
|
+
for list_name in _GTD_LISTS:
|
|
600
|
+
items = _load_list(list_name)
|
|
601
|
+
all_items[list_name] = items
|
|
602
|
+
review["counts"][list_name] = len(items)
|
|
603
|
+
review["total"] += len(items)
|
|
604
|
+
|
|
605
|
+
# Archive count
|
|
606
|
+
archive = _load_archive()
|
|
607
|
+
review["counts"]["archive"] = len(archive)
|
|
608
|
+
|
|
609
|
+
# Oldest items across all lists (top 5)
|
|
610
|
+
every_item = []
|
|
611
|
+
for list_name, items in all_items.items():
|
|
612
|
+
for it in items:
|
|
613
|
+
every_item.append({**it, "_list": list_name})
|
|
614
|
+
|
|
615
|
+
every_item.sort(key=lambda x: x.get("created_at", ""))
|
|
616
|
+
review["oldest_items"] = [
|
|
617
|
+
{"id": it["id"], "text": it.get("text", "")[:60], "list": it["_list"],
|
|
618
|
+
"created_at": it.get("created_at", "")}
|
|
619
|
+
for it in every_item[:5]
|
|
620
|
+
]
|
|
621
|
+
|
|
622
|
+
# Items waiting longest (from waiting-for)
|
|
623
|
+
waiting = all_items.get("waiting-for", [])
|
|
624
|
+
waiting_sorted = sorted(waiting, key=lambda x: x.get("created_at", ""))
|
|
625
|
+
review["longest_waiting"] = [
|
|
626
|
+
{"id": it["id"], "text": it.get("text", "")[:60],
|
|
627
|
+
"delegate_to": it.get("delegate_to", ""),
|
|
628
|
+
"created_at": it.get("created_at", "")}
|
|
629
|
+
for it in waiting_sorted[:5]
|
|
630
|
+
]
|
|
631
|
+
|
|
632
|
+
# Stale projects (no activity in 7+ days)
|
|
633
|
+
projects = all_items.get("projects", [])
|
|
634
|
+
stale = []
|
|
635
|
+
for proj in projects:
|
|
636
|
+
last_touch = proj.get("moved_at") or proj.get("clarified_at") or proj.get("created_at", "")
|
|
637
|
+
if last_touch:
|
|
638
|
+
try:
|
|
639
|
+
ts = datetime.fromisoformat(last_touch.replace("Z", "+00:00"))
|
|
640
|
+
age_days = (now - ts).days
|
|
641
|
+
if age_days >= 7:
|
|
642
|
+
stale.append({
|
|
643
|
+
"id": proj["id"],
|
|
644
|
+
"text": proj.get("text", "")[:60],
|
|
645
|
+
"days_stale": age_days,
|
|
646
|
+
})
|
|
647
|
+
except (ValueError, TypeError):
|
|
648
|
+
pass
|
|
649
|
+
stale.sort(key=lambda x: x["days_stale"], reverse=True)
|
|
650
|
+
review["stale_projects"] = stale[:5]
|
|
651
|
+
|
|
652
|
+
# Inbox items needing clarification
|
|
653
|
+
review["inbox_needs_clarify"] = review["counts"].get("inbox", 0)
|
|
654
|
+
|
|
655
|
+
return _json_response(review)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
_PRIORITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
async def _handle_gtd_next(args: dict) -> list[TextContent]:
|
|
662
|
+
"""View next actions filtered by context, energy, and/or priority."""
|
|
663
|
+
context_filter = args.get("context")
|
|
664
|
+
energy_filter = args.get("energy")
|
|
665
|
+
priority_filter = args.get("priority")
|
|
666
|
+
limit = args.get("limit", 10)
|
|
667
|
+
if not isinstance(limit, int) or limit < 1:
|
|
668
|
+
limit = 10
|
|
669
|
+
|
|
670
|
+
items = _load_list("next-actions")
|
|
671
|
+
|
|
672
|
+
# Apply filters
|
|
673
|
+
if context_filter:
|
|
674
|
+
items = [it for it in items if it.get("context") == context_filter]
|
|
675
|
+
if energy_filter:
|
|
676
|
+
if energy_filter not in _VALID_ENERGIES:
|
|
677
|
+
return _error_response(
|
|
678
|
+
f"Invalid energy '{energy_filter}'. Valid: {', '.join(sorted(_VALID_ENERGIES))}"
|
|
679
|
+
)
|
|
680
|
+
items = [it for it in items if it.get("energy") == energy_filter]
|
|
681
|
+
if priority_filter:
|
|
682
|
+
if priority_filter not in _VALID_PRIORITIES:
|
|
683
|
+
return _error_response(
|
|
684
|
+
f"Invalid priority '{priority_filter}'. Valid: {', '.join(sorted(_VALID_PRIORITIES))}"
|
|
685
|
+
)
|
|
686
|
+
items = [it for it in items if it.get("priority") == priority_filter]
|
|
687
|
+
|
|
688
|
+
# Sort: priority (critical > high > medium > low), then oldest first
|
|
689
|
+
items.sort(key=lambda x: (
|
|
690
|
+
_PRIORITY_ORDER.get(x.get("priority", "low"), 3),
|
|
691
|
+
x.get("created_at", ""),
|
|
692
|
+
))
|
|
693
|
+
|
|
694
|
+
total = len(items)
|
|
695
|
+
items = items[:limit]
|
|
696
|
+
|
|
697
|
+
return _json_response({
|
|
698
|
+
"items": items,
|
|
699
|
+
"total": total,
|
|
700
|
+
"showing": len(items),
|
|
701
|
+
"filters": {
|
|
702
|
+
"context": context_filter,
|
|
703
|
+
"energy": energy_filter,
|
|
704
|
+
"priority": priority_filter,
|
|
705
|
+
},
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
async def _handle_gtd_projects(args: dict) -> list[TextContent]:
|
|
710
|
+
"""View GTD projects filtered by status."""
|
|
711
|
+
status_filter = args.get("status", "all")
|
|
712
|
+
limit = args.get("limit", 10)
|
|
713
|
+
if not isinstance(limit, int) or limit < 1:
|
|
714
|
+
limit = 10
|
|
715
|
+
if status_filter not in ("active", "stale", "all"):
|
|
716
|
+
status_filter = "all"
|
|
717
|
+
|
|
718
|
+
now = datetime.now(timezone.utc)
|
|
719
|
+
projects = _load_list("projects")
|
|
720
|
+
|
|
721
|
+
result_items = []
|
|
722
|
+
for proj in projects:
|
|
723
|
+
last_touch = (
|
|
724
|
+
proj.get("moved_at")
|
|
725
|
+
or proj.get("clarified_at")
|
|
726
|
+
or proj.get("created_at", "")
|
|
727
|
+
)
|
|
728
|
+
days_since = None
|
|
729
|
+
is_stale = False
|
|
730
|
+
if last_touch:
|
|
731
|
+
try:
|
|
732
|
+
ts = datetime.fromisoformat(last_touch.replace("Z", "+00:00"))
|
|
733
|
+
days_since = (now - ts).days
|
|
734
|
+
is_stale = days_since >= 7
|
|
735
|
+
except (ValueError, TypeError):
|
|
736
|
+
pass
|
|
737
|
+
|
|
738
|
+
proj_status = "stale" if is_stale else "active"
|
|
739
|
+
|
|
740
|
+
if status_filter != "all" and proj_status != status_filter:
|
|
741
|
+
continue
|
|
742
|
+
|
|
743
|
+
result_items.append({
|
|
744
|
+
"id": proj.get("id", ""),
|
|
745
|
+
"text": proj.get("text", ""),
|
|
746
|
+
"status": proj_status,
|
|
747
|
+
"priority": proj.get("priority"),
|
|
748
|
+
"energy": proj.get("energy"),
|
|
749
|
+
"context": proj.get("context"),
|
|
750
|
+
"days_since_activity": days_since,
|
|
751
|
+
"created_at": proj.get("created_at", ""),
|
|
752
|
+
"next_action": proj.get("text", "")[:60],
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
total = len(result_items)
|
|
756
|
+
result_items = result_items[:limit]
|
|
757
|
+
|
|
758
|
+
return _json_response({
|
|
759
|
+
"projects": result_items,
|
|
760
|
+
"total": total,
|
|
761
|
+
"showing": len(result_items),
|
|
762
|
+
"filter": status_filter,
|
|
763
|
+
})
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
async def _handle_gtd_waiting(args: dict) -> list[TextContent]:
|
|
767
|
+
"""View waiting-for items sorted by longest waiting."""
|
|
768
|
+
limit = args.get("limit", 10)
|
|
769
|
+
if not isinstance(limit, int) or limit < 1:
|
|
770
|
+
limit = 10
|
|
771
|
+
|
|
772
|
+
now = datetime.now(timezone.utc)
|
|
773
|
+
items = _load_list("waiting-for")
|
|
774
|
+
|
|
775
|
+
# Sort oldest first (longest waiting)
|
|
776
|
+
items.sort(key=lambda x: x.get("created_at", ""))
|
|
777
|
+
|
|
778
|
+
result_items = []
|
|
779
|
+
for it in items:
|
|
780
|
+
created = it.get("created_at", "")
|
|
781
|
+
waiting_days = None
|
|
782
|
+
if created:
|
|
783
|
+
try:
|
|
784
|
+
ts = datetime.fromisoformat(created.replace("Z", "+00:00"))
|
|
785
|
+
waiting_days = (now - ts).days
|
|
786
|
+
except (ValueError, TypeError):
|
|
787
|
+
pass
|
|
788
|
+
|
|
789
|
+
result_items.append({
|
|
790
|
+
"id": it.get("id", ""),
|
|
791
|
+
"text": it.get("text", ""),
|
|
792
|
+
"delegate_to": it.get("delegate_to", ""),
|
|
793
|
+
"context": it.get("context"),
|
|
794
|
+
"priority": it.get("priority"),
|
|
795
|
+
"created_at": created,
|
|
796
|
+
"waiting_days": waiting_days,
|
|
797
|
+
"waiting_since": f"{waiting_days} day(s)" if waiting_days is not None else "unknown",
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
total = len(result_items)
|
|
801
|
+
result_items = result_items[:limit]
|
|
802
|
+
|
|
803
|
+
return _json_response({
|
|
804
|
+
"items": result_items,
|
|
805
|
+
"total": total,
|
|
806
|
+
"showing": len(result_items),
|
|
807
|
+
})
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
HANDLERS: dict = {
|
|
811
|
+
"gtd_capture": _handle_gtd_capture,
|
|
812
|
+
"gtd_inbox": _handle_gtd_inbox,
|
|
813
|
+
"gtd_status": _handle_gtd_status,
|
|
814
|
+
"gtd_clarify": _handle_gtd_clarify,
|
|
815
|
+
"gtd_move": _handle_gtd_move,
|
|
816
|
+
"gtd_done": _handle_gtd_done,
|
|
817
|
+
"gtd_review": _handle_gtd_review,
|
|
818
|
+
"gtd_next": _handle_gtd_next,
|
|
819
|
+
"gtd_projects": _handle_gtd_projects,
|
|
820
|
+
"gtd_waiting": _handle_gtd_waiting,
|
|
821
|
+
}
|