@smilintux/skcapstone 0.9.0 → 0.12.5
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 +10 -4
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +9 -2
- package/.openclaw-workspace.json +2 -2
- package/CLAUDE.md +37 -0
- package/MISSION.md +17 -2
- package/README.md +282 -3
- package/docker/Dockerfile +7 -7
- package/docker/compose-templates/dev-team.yml +12 -12
- package/docker/compose-templates/mini-team.yml +9 -9
- package/docker/compose-templates/ops-team.yml +10 -10
- package/docker/compose-templates/research-team.yml +10 -10
- package/docker/entrypoint.sh +4 -4
- package/docs/ADR-optional-integration-backbone.md +181 -0
- package/docs/ARCHITECTURE.md +186 -43
- package/docs/BOND_WITH_GROK.md +6 -6
- package/docs/CUSTOM_AGENT.md +278 -1
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +10 -7
- package/docs/QUICKSTART.md +10 -6
- package/docs/SKJOULE_ARCHITECTURE.md +3 -3
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/docs/sk-integration-HANDOFF.md +117 -0
- package/docs/skscheduler.md +155 -0
- package/docs/superpowers/examples/jobs.yaml +31 -0
- package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
- package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
- package/examples/custom-bond-template.json +1 -1
- package/examples/grok-feb.json +1 -1
- package/examples/queen-ava-feb.json +1 -1
- package/launchd/com.skcapstone.daemon.plist +52 -0
- package/launchd/com.skcapstone.memory-compress.plist +45 -0
- package/launchd/com.skcapstone.skcomms-heartbeat.plist +33 -0
- package/launchd/com.skcapstone.skcomms-queue-drain.plist +34 -0
- package/launchd/install-launchd.sh +156 -0
- package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
- package/package.json +1 -1
- package/pyproject.toml +16 -10
- package/scripts/archive-sessions.sh +95 -0
- package/scripts/check-updates.py +4 -4
- package/scripts/install-bundle.sh +8 -8
- package/scripts/install.ps1 +12 -11
- package/scripts/install.sh +196 -11
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/notion-api.py +259 -0
- package/scripts/nvidia-proxy.mjs +908 -0
- package/scripts/proxy-monitor.sh +89 -0
- package/scripts/refresh-anthropic-token.sh +172 -0
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/skgateway.mjs +856 -0
- package/scripts/telegram-catchup-all.sh +147 -0
- package/scripts/verify_install.sh +2 -2
- package/scripts/wargov-ufo-capture/README.md +43 -0
- package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
- package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
- package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
- package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
- package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
- package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
- package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
- package/scripts/watch-anthropic-token.sh +212 -0
- package/scripts/windows/install-tasks.ps1 +7 -7
- package/scripts/windows/skcapstone-task.xml +1 -1
- package/src/skcapstone/__init__.py +45 -3
- package/src/skcapstone/_cli_monolith.py +20 -15
- package/src/skcapstone/activity.py +5 -1
- package/src/skcapstone/agent_card.py +3 -2
- package/src/skcapstone/api.py +41 -40
- package/src/skcapstone/auction.py +14 -11
- package/src/skcapstone/backup.py +2 -1
- package/src/skcapstone/blueprint_registry.py +4 -3
- package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/changelog.py +1 -1
- package/src/skcapstone/chat.py +22 -17
- package/src/skcapstone/cli/__init__.py +9 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/alerts.py +25 -4
- package/src/skcapstone/cli/bench.py +15 -15
- package/src/skcapstone/cli/chat.py +7 -4
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/context_cmd.py +18 -4
- package/src/skcapstone/cli/daemon.py +121 -42
- package/src/skcapstone/cli/gtd.py +26 -1
- package/src/skcapstone/cli/housekeeping.py +3 -3
- package/src/skcapstone/cli/identity_cmd.py +378 -0
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +8 -6
- package/src/skcapstone/cli/peers_dir.py +1 -1
- package/src/skcapstone/cli/register_cmd.py +29 -3
- package/src/skcapstone/cli/scheduler_cmd.py +167 -0
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -29
- package/src/skcapstone/cli/shell_cmd.py +53 -1
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +8 -5
- package/src/skcapstone/cli/status.py +37 -11
- package/src/skcapstone/cli/telegram.py +21 -0
- package/src/skcapstone/cli/test_cmd.py +5 -5
- package/src/skcapstone/cli/test_connection.py +2 -2
- package/src/skcapstone/cli/upgrade_cmd.py +23 -14
- package/src/skcapstone/cli/version_cmd.py +1 -1
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/cloud9_bridge.py +14 -14
- package/src/skcapstone/codex_setup.py +255 -0
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +313 -273
- package/src/skcapstone/context_loader.py +121 -0
- package/src/skcapstone/coord_federation.py +2 -1
- package/src/skcapstone/coordination.py +23 -6
- package/src/skcapstone/crush_integration.py +2 -1
- package/src/skcapstone/daemon.py +151 -88
- package/src/skcapstone/dashboard.py +10 -10
- package/src/skcapstone/data/sk-agent-picker.sh +421 -0
- package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
- package/src/skcapstone/data/systemd/skcapstone.service +37 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
- package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
- package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
- package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -0
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
- package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
- package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
- package/src/skcapstone/defaults/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +43 -20
- package/src/skcapstone/doctor.py +941 -22
- package/src/skcapstone/dreaming.py +1183 -109
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +4 -3
- package/src/skcapstone/fuse_mount.py +35 -25
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +34 -30
- package/src/skcapstone/housekeeping.py +14 -14
- package/src/skcapstone/install_wizard.py +209 -7
- package/src/skcapstone/itil.py +13 -4
- package/src/skcapstone/kms_scheduler.py +10 -8
- package/src/skcapstone/launchd.py +426 -0
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +341 -49
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +11 -8
- package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
- package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
- package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/memory_curator.py +1 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/metrics.py +30 -16
- package/src/skcapstone/migrate_memories.py +4 -3
- package/src/skcapstone/migrate_multi_agent.py +8 -7
- package/src/skcapstone/models.py +47 -5
- package/src/skcapstone/notifications.py +42 -18
- package/src/skcapstone/onboard.py +1000 -126
- package/src/skcapstone/operator_link.py +170 -0
- package/src/skcapstone/peer_directory.py +4 -4
- package/src/skcapstone/peers.py +19 -19
- package/src/skcapstone/pillars/__init__.py +7 -5
- package/src/skcapstone/pillars/consciousness.py +191 -0
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/pillars/sync.py +2 -2
- package/src/skcapstone/preflight.py +3 -3
- package/src/skcapstone/providers/docker.py +28 -28
- package/src/skcapstone/register.py +6 -6
- package/src/skcapstone/registry_client.py +5 -4
- package/src/skcapstone/runtime.py +14 -3
- package/src/skcapstone/scheduled_tasks.py +254 -19
- package/src/skcapstone/scheduler_jobs.py +456 -0
- package/src/skcapstone/scheduler_runner.py +239 -0
- package/src/skcapstone/scheduler_state.py +162 -0
- package/src/skcapstone/sdk.py +310 -0
- package/src/skcapstone/service_health.py +279 -39
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/session_capture.py +1 -1
- package/src/skcapstone/shell.py +7 -1
- package/src/skcapstone/soul.py +3 -1
- package/src/skcapstone/soul_switch.py +3 -1
- package/src/skcapstone/summary.py +6 -6
- package/src/skcapstone/sync_engine.py +15 -15
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +72 -21
- package/src/skcapstone/team_comms.py +8 -8
- package/src/skcapstone/team_engine.py +1 -1
- package/src/skcapstone/testrunner.py +3 -3
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +15 -6
- package/src/skcapstone/uninstall_wizard.py +11 -3
- package/src/skcapstone/version_check.py +8 -4
- package/src/skcapstone/warmth_anchor.py +4 -2
- package/src/skcapstone/whoami.py +4 -4
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomms-heartbeat.service +21 -0
- package/systemd/skcomms-heartbeat.timer +12 -0
- package/systemd/skcomms-queue-drain.service +17 -0
- package/systemd/skcomms-queue-drain.timer +12 -0
- package/tests/conftest.py +39 -0
- package/tests/integration/test_consciousness_e2e.py +39 -39
- package/tests/test_agent_card.py +1 -1
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_alerts_consumer_topics.py +27 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_chat.py +6 -6
- package/tests/test_claude_md.py +2 -2
- package/tests/test_cli_skills.py +10 -10
- package/tests/test_cli_test_cmd.py +4 -4
- package/tests/test_cli_test_connection.py +1 -1
- package/tests/test_cloud9_bridge.py +6 -6
- package/tests/test_consciousness_e2e.py +1 -1
- package/tests/test_consciousness_loop.py +10 -10
- package/tests/test_coordination.py +25 -0
- package/tests/test_cross_package.py +21 -21
- package/tests/test_daemon.py +4 -4
- package/tests/test_daemon_shutdown.py +1 -1
- package/tests/test_docker_provider.py +29 -29
- package/tests/test_doctor.py +400 -0
- package/tests/test_doctor_skscheduler.py +50 -0
- package/tests/test_dreaming_engine.py +147 -0
- package/tests/test_dreaming_gtd_capture.py +35 -0
- package/tests/test_e2e_automated.py +8 -5
- package/tests/test_fuse_mount.py +10 -10
- package/tests/test_gtd_brief.py +46 -0
- package/tests/test_gtd_malformed_tolerance.py +31 -0
- package/tests/test_housekeeping.py +15 -15
- package/tests/test_identity_migrate.py +251 -0
- package/tests/test_integration_backbone.py +598 -0
- package/tests/test_itil_gtd_lifecycle.py +37 -0
- package/tests/test_jobs_dropins.py +84 -0
- package/tests/test_mcp_server.py +82 -37
- package/tests/test_models.py +48 -4
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_notifications.py +122 -32
- package/tests/test_onboard.py +63 -75
- package/tests/test_operator_link.py +78 -0
- package/tests/test_peers.py +14 -14
- package/tests/test_pillars.py +98 -0
- package/tests/test_preflight.py +3 -3
- package/tests/test_runtime.py +21 -0
- package/tests/test_scheduled_tasks.py +11 -6
- package/tests/test_scheduler_cli.py +47 -0
- package/tests/test_scheduler_features.py +133 -0
- package/tests/test_scheduler_integration.py +87 -0
- package/tests/test_scheduler_jobs.py +155 -0
- package/tests/test_scheduler_runner.py +64 -0
- package/tests/test_scheduler_state.py +57 -0
- package/tests/test_sdk.py +70 -0
- package/tests/test_service_health_incidents.py +34 -0
- package/tests/test_service_registry.py +52 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_snapshots.py +4 -4
- package/tests/test_sync_pipeline.py +26 -26
- package/tests/test_team_comms.py +2 -2
- package/tests/test_testrunner.py +2 -2
- package/tests/test_trust_graph.py +18 -0
- package/tests/test_unified_search.py +2 -2
- package/tests/test_version_check.py +10 -0
- package/tests/test_version_cmd.py +8 -8
- package/tests/test_whoami.py +1 -1
- package/systemd/skcomm-heartbeat.service +0 -18
- package/systemd/skcomm-queue-drain.service +0 -17
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
|
@@ -423,7 +423,7 @@ def register_memory_commands(main: click.Group) -> None:
|
|
|
423
423
|
@memory.command("rehydrate")
|
|
424
424
|
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
425
425
|
@click.option("--agent", "-a", default=None,
|
|
426
|
-
help="Agent name (default:
|
|
426
|
+
help="Agent name (default: active agent).")
|
|
427
427
|
@click.option("--febs-only", is_flag=True, help="Only ingest FEB files (trust rehydration).")
|
|
428
428
|
@click.option("--memories-only", is_flag=True, help="Only ingest flat-file memories into backends.")
|
|
429
429
|
@click.option("--force", is_flag=True, help="Re-ingest even if already in backend.")
|
|
@@ -439,7 +439,9 @@ def register_memory_commands(main: click.Group) -> None:
|
|
|
439
439
|
import os
|
|
440
440
|
from ..models import MemoryLayer
|
|
441
441
|
|
|
442
|
-
|
|
442
|
+
from .. import active_agent_name
|
|
443
|
+
|
|
444
|
+
agent_name = agent or os.environ.get("SKCAPSTONE_AGENT") or active_agent_name()
|
|
443
445
|
home_path = Path(home).expanduser()
|
|
444
446
|
agent_home = home_path / "agents" / agent_name
|
|
445
447
|
|
|
@@ -463,8 +465,8 @@ def register_memory_commands(main: click.Group) -> None:
|
|
|
463
465
|
try:
|
|
464
466
|
from ..memory_adapter import get_unified, entry_to_memory
|
|
465
467
|
unified = get_unified()
|
|
466
|
-
except Exception:
|
|
467
|
-
|
|
468
|
+
except Exception as exc:
|
|
469
|
+
logger.warning("Memory adapter unavailable, falling back to file-only mode: %s", exc)
|
|
468
470
|
|
|
469
471
|
for layer in MemoryLayer:
|
|
470
472
|
layer_dir = mem_dir / layer.value
|
|
@@ -481,8 +483,8 @@ def register_memory_commands(main: click.Group) -> None:
|
|
|
481
483
|
if existing:
|
|
482
484
|
skipped += 1
|
|
483
485
|
continue
|
|
484
|
-
except Exception:
|
|
485
|
-
|
|
486
|
+
except Exception as exc:
|
|
487
|
+
logger.debug("Failed to check existing memory %s: %s", mem_id, exc)
|
|
486
488
|
|
|
487
489
|
if unified:
|
|
488
490
|
from ..models import MemoryEntry
|
|
@@ -19,7 +19,7 @@ def register_peers_dir_commands(main: click.Group) -> None:
|
|
|
19
19
|
def peers_dir():
|
|
20
20
|
"""Peer transport directory — routing addresses for the mesh.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
SKComms transport endpoints. For identity/trust peers, see 'peer'."""
|
|
23
23
|
|
|
24
24
|
@peers_dir.command("list")
|
|
25
25
|
@click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Register command — auto-register SK* skills and MCP servers.
|
|
2
2
|
|
|
3
3
|
Detects the user's environments (OpenClaw, Claude Code, Cursor, VS Code,
|
|
4
|
-
OpenCode CLI, mcporter) and registers SKILL.md symlinks + MCP server entries.
|
|
4
|
+
OpenCode CLI, Codex, mcporter) and registers SKILL.md symlinks + MCP server entries.
|
|
5
5
|
|
|
6
6
|
Commands:
|
|
7
7
|
skcapstone register — register all SK* packages
|
|
@@ -49,7 +49,7 @@ def register_register_commands(main: click.Group) -> None:
|
|
|
49
49
|
"""Register all SK* skills and MCP servers in detected environments.
|
|
50
50
|
|
|
51
51
|
Auto-detects your development environments (Claude Code, Cursor,
|
|
52
|
-
VS Code, OpenClaw, OpenCode, mcporter) and ensures all SK* skill
|
|
52
|
+
VS Code, OpenClaw, OpenCode, Codex, mcporter) and ensures all SK* skill
|
|
53
53
|
manifests and MCP server entries are properly configured.
|
|
54
54
|
|
|
55
55
|
Examples:
|
|
@@ -91,12 +91,23 @@ def register_register_commands(main: click.Group) -> None:
|
|
|
91
91
|
dry_run=dry_run,
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
+
# Register Claude Code hooks
|
|
95
|
+
if not dry_run:
|
|
96
|
+
try:
|
|
97
|
+
from skmemory.register import register_hooks
|
|
98
|
+
register_hooks(install_hooks=True)
|
|
99
|
+
except ImportError:
|
|
100
|
+
pass
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
|
|
94
104
|
# Display results
|
|
95
105
|
from rich.table import Table
|
|
96
106
|
|
|
97
107
|
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
98
108
|
table.add_column("Package", style="cyan")
|
|
99
109
|
table.add_column("Skill", style="dim")
|
|
110
|
+
table.add_column("Codex")
|
|
100
111
|
table.add_column("MCP")
|
|
101
112
|
table.add_column("OpenClaw Plugin")
|
|
102
113
|
|
|
@@ -133,6 +144,21 @@ def register_register_commands(main: click.Group) -> None:
|
|
|
133
144
|
else:
|
|
134
145
|
mcp_str = str(mcp_info)
|
|
135
146
|
|
|
147
|
+
codex_info = pkg_result.get("codex_skill", {})
|
|
148
|
+
codex_action = codex_info.get("action", "")
|
|
149
|
+
if codex_action == "created":
|
|
150
|
+
codex_str = "[green]created[/]"
|
|
151
|
+
elif codex_action == "exists":
|
|
152
|
+
codex_str = "[dim]exists[/]"
|
|
153
|
+
elif codex_action == "dry-run":
|
|
154
|
+
codex_str = "[yellow]would create[/]"
|
|
155
|
+
elif codex_action == "error":
|
|
156
|
+
codex_str = f"[red]{codex_info.get('error', 'error')}[/]"
|
|
157
|
+
elif not codex_action:
|
|
158
|
+
codex_str = "[dim]—[/]"
|
|
159
|
+
else:
|
|
160
|
+
codex_str = f"[dim]{codex_action}[/]"
|
|
161
|
+
|
|
136
162
|
plugin_action = pkg_result.get("openclaw_plugin", "")
|
|
137
163
|
if plugin_action == "created":
|
|
138
164
|
plugin_str = "[green]created[/]"
|
|
@@ -147,7 +173,7 @@ def register_register_commands(main: click.Group) -> None:
|
|
|
147
173
|
else:
|
|
148
174
|
plugin_str = f"[dim]{plugin_action}[/]"
|
|
149
175
|
|
|
150
|
-
table.add_row(name, skill_str, mcp_str, plugin_str)
|
|
176
|
+
table.add_row(name, skill_str, codex_str, mcp_str, plugin_str)
|
|
151
177
|
|
|
152
178
|
console.print(table)
|
|
153
179
|
console.print()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""`skcapstone scheduler` — manage the unified job scheduler."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import socket
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from .. import AGENT_HOME
|
|
12
|
+
from ..scheduler_jobs import (
|
|
13
|
+
load_jobs,
|
|
14
|
+
load_jobs_with_dropins,
|
|
15
|
+
current_host_aliases,
|
|
16
|
+
job_runs_here,
|
|
17
|
+
)
|
|
18
|
+
from ..scheduler_runner import JobRunner
|
|
19
|
+
from ..scheduler_state import SchedulerState
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _home() -> Path:
|
|
23
|
+
"""Return the effective SKCAPSTONE_HOME path.
|
|
24
|
+
|
|
25
|
+
Reads from the ``SKCAPSTONE_HOME`` environment variable when set,
|
|
26
|
+
falling back to the package-level ``AGENT_HOME`` constant.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Resolved :class:`~pathlib.Path` for the agent home directory.
|
|
30
|
+
"""
|
|
31
|
+
return Path(os.environ.get("SKCAPSTONE_HOME", AGENT_HOME))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _jobs_path() -> Path:
|
|
35
|
+
"""Return the path to the synced ``jobs.yaml`` registry.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
``<SKCAPSTONE_HOME>/config/jobs.yaml`` as a :class:`~pathlib.Path`.
|
|
39
|
+
"""
|
|
40
|
+
return _home() / "config" / "jobs.yaml"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def register_scheduler_commands(main: click.Group) -> None:
|
|
44
|
+
"""Register the ``scheduler`` command group onto *main*.
|
|
45
|
+
|
|
46
|
+
Adds the following sub-commands:
|
|
47
|
+
|
|
48
|
+
- ``scheduler list`` — list all configured jobs with run status.
|
|
49
|
+
- ``scheduler status`` — show last-run state for this node.
|
|
50
|
+
- ``scheduler run`` — execute a job immediately.
|
|
51
|
+
- ``scheduler logs`` — tail the most recent log for a job.
|
|
52
|
+
- ``scheduler enable`` — enable a job in ``jobs.yaml``.
|
|
53
|
+
- ``scheduler disable`` — disable a job in ``jobs.yaml``.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
main: The top-level :class:`click.Group` to attach commands to.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
@main.group("scheduler")
|
|
60
|
+
def scheduler() -> None:
|
|
61
|
+
"""Manage the unified job scheduler (skscheduler)."""
|
|
62
|
+
|
|
63
|
+
@scheduler.command("list")
|
|
64
|
+
def list_jobs() -> None:
|
|
65
|
+
"""List all configured jobs and where they run."""
|
|
66
|
+
jobs = load_jobs_with_dropins(_jobs_path())
|
|
67
|
+
if not jobs:
|
|
68
|
+
click.echo("No jobs configured.")
|
|
69
|
+
return
|
|
70
|
+
here = current_host_aliases()
|
|
71
|
+
for j in jobs:
|
|
72
|
+
sched = j.schedule or (f"every {int(j.every_seconds)}s" if j.every_seconds else "-")
|
|
73
|
+
mark = "x" if (j.enabled and job_runs_here(j, here)) else " "
|
|
74
|
+
click.echo(f"[{mark}] {j.name:24s} {j.type:6s} {sched:18s} nodes={j.nodes}")
|
|
75
|
+
|
|
76
|
+
@scheduler.command("status")
|
|
77
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
78
|
+
def status(as_json: bool) -> None:
|
|
79
|
+
"""Show last-run status for this node."""
|
|
80
|
+
st = SchedulerState(root=_home(), hostname=socket.gethostname())
|
|
81
|
+
data = st.all()
|
|
82
|
+
if as_json:
|
|
83
|
+
click.echo(json.dumps(data, indent=2))
|
|
84
|
+
return
|
|
85
|
+
if not data:
|
|
86
|
+
click.echo("No run history on this node yet.")
|
|
87
|
+
return
|
|
88
|
+
for name, rec in data.items():
|
|
89
|
+
click.echo(
|
|
90
|
+
f"{name:24s} last={rec.get('last_run')} status={rec.get('last_status')} "
|
|
91
|
+
f"runs={rec.get('run_count')} errors={rec.get('error_count')}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
@scheduler.command("run")
|
|
95
|
+
@click.argument("job_name")
|
|
96
|
+
def run_now(job_name: str) -> None:
|
|
97
|
+
"""Run a job now on this node (manual override; ignores schedule and affinity)."""
|
|
98
|
+
jobs = {j.name: j for j in load_jobs_with_dropins(_jobs_path())}
|
|
99
|
+
job = jobs.get(job_name)
|
|
100
|
+
if not job:
|
|
101
|
+
raise click.ClickException(f"Unknown job: {job_name}")
|
|
102
|
+
runner = JobRunner(log_dir=_home() / "scheduler" / socket.gethostname() / "logs")
|
|
103
|
+
result = runner.run(job)
|
|
104
|
+
# Record state + fire the job's notify policy so a manual run is observable in
|
|
105
|
+
# `scheduler status` and exercises the sk-alert hook (same as the scheduled path).
|
|
106
|
+
from datetime import datetime, timezone
|
|
107
|
+
from ..scheduled_tasks import TaskScheduler
|
|
108
|
+
SchedulerState(_home(), socket.gethostname()).record_run(
|
|
109
|
+
job.name, now=datetime.now(timezone.utc), ok=result.ok, error=result.error)
|
|
110
|
+
TaskScheduler._maybe_notify(job, result, attempts=1)
|
|
111
|
+
if result.output:
|
|
112
|
+
click.echo(result.output.strip())
|
|
113
|
+
if not result.ok:
|
|
114
|
+
raise click.ClickException(f"Job failed: {result.error}")
|
|
115
|
+
click.echo(f"OK {job_name} done")
|
|
116
|
+
|
|
117
|
+
@scheduler.command("logs")
|
|
118
|
+
@click.argument("job_name")
|
|
119
|
+
@click.option("--tail", default=40, show_default=True, help="Number of lines to show.")
|
|
120
|
+
def logs(job_name: str, tail: int) -> None:
|
|
121
|
+
"""Show the latest log for a job on this node."""
|
|
122
|
+
log_dir = _home() / "scheduler" / socket.gethostname() / "logs"
|
|
123
|
+
matches = sorted(log_dir.glob(f"{job_name}-*.log")) if log_dir.exists() else []
|
|
124
|
+
if not matches:
|
|
125
|
+
click.echo(f"No logs for '{job_name}'.")
|
|
126
|
+
return
|
|
127
|
+
lines = matches[-1].read_text(encoding="utf-8").splitlines()
|
|
128
|
+
click.echo("\n".join(lines[-tail:]))
|
|
129
|
+
|
|
130
|
+
@scheduler.command("enable")
|
|
131
|
+
@click.argument("job_name")
|
|
132
|
+
def enable(job_name: str) -> None:
|
|
133
|
+
"""Enable a job (sets enabled: true in jobs.yaml)."""
|
|
134
|
+
_set_enabled(job_name, True)
|
|
135
|
+
click.echo(f"enabled {job_name}")
|
|
136
|
+
|
|
137
|
+
@scheduler.command("disable")
|
|
138
|
+
@click.argument("job_name")
|
|
139
|
+
def disable(job_name: str) -> None:
|
|
140
|
+
"""Disable a job (sets enabled: false in jobs.yaml)."""
|
|
141
|
+
_set_enabled(job_name, False)
|
|
142
|
+
click.echo(f"disabled {job_name}")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _set_enabled(job_name: str, value: bool) -> None:
|
|
146
|
+
"""Set the ``enabled`` flag for *job_name* in ``jobs.yaml``.
|
|
147
|
+
|
|
148
|
+
Reads the current ``jobs.yaml``, toggles the ``enabled`` field for the
|
|
149
|
+
named job, and writes the file back atomically via
|
|
150
|
+
:func:`yaml.safe_dump`.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
job_name: The job key to update.
|
|
154
|
+
value: ``True`` to enable the job; ``False`` to disable.
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
click.ClickException: If *job_name* is not found in the config.
|
|
158
|
+
"""
|
|
159
|
+
import yaml
|
|
160
|
+
|
|
161
|
+
path = _jobs_path()
|
|
162
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
163
|
+
jobs = data.get("jobs") or {}
|
|
164
|
+
if job_name not in jobs:
|
|
165
|
+
raise click.ClickException(f"Unknown job: {job_name}")
|
|
166
|
+
jobs[job_name]["enabled"] = value
|
|
167
|
+
path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import sys
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
@@ -125,3 +126,27 @@ def register_session_commands(main: click.Group) -> None:
|
|
|
125
126
|
for src, count in sorted(by_source.items(), key=lambda x: -x[1]):
|
|
126
127
|
console.print(f" {src}: {count}")
|
|
127
128
|
console.print()
|
|
129
|
+
|
|
130
|
+
@session.command("briefing")
|
|
131
|
+
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
132
|
+
@click.option(
|
|
133
|
+
"--format",
|
|
134
|
+
"fmt",
|
|
135
|
+
type=click.Choice(["text", "json"]),
|
|
136
|
+
default="text",
|
|
137
|
+
help="Output format (default: text).",
|
|
138
|
+
)
|
|
139
|
+
@click.option("--memories", "-n", default=10, help="Max recent memories to include.")
|
|
140
|
+
def session_briefing(home: str, fmt: str, memories: int):
|
|
141
|
+
"""Show a native startup briefing for sovereign sessions.
|
|
142
|
+
|
|
143
|
+
Merges SKCapstone context with the current HammerTime legal/case
|
|
144
|
+
briefing when available, so any client can consume one startup payload.
|
|
145
|
+
"""
|
|
146
|
+
from ..session_briefing import build_session_briefing, format_session_briefing_text
|
|
147
|
+
|
|
148
|
+
payload = build_session_briefing(Path(home).expanduser(), memory_limit=memories)
|
|
149
|
+
if fmt == "json":
|
|
150
|
+
click.echo(json.dumps(payload, indent=2, default=str))
|
|
151
|
+
return
|
|
152
|
+
click.echo(format_session_briefing_text(payload))
|
|
@@ -26,16 +26,23 @@ from ..runtime import get_runtime
|
|
|
26
26
|
from rich.panel import Panel
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def _get_claude_template_dir() -> Path:
|
|
30
|
+
"""Return the bundled defaults/claude skeleton directory."""
|
|
31
|
+
return Path(__file__).parent.parent / "defaults" / "claude"
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
def _write_global_claude_md(home_path: Path, agent_name: str) -> Optional[Path]:
|
|
30
|
-
"""Write ~/.claude/CLAUDE.md
|
|
35
|
+
"""Write ~/.claude/CLAUDE.md from the bundled skeleton template.
|
|
36
|
+
|
|
37
|
+
The template lives at defaults/claude/CLAUDE.md inside the package.
|
|
38
|
+
{{AGENT_NAME}} is substituted with the actual agent name.
|
|
39
|
+
If the template is missing, falls back to a minimal generated file.
|
|
40
|
+
"""
|
|
31
41
|
import platform
|
|
32
42
|
|
|
33
43
|
if platform.system() == "Windows":
|
|
34
44
|
appdata = os.environ.get("APPDATA", "")
|
|
35
|
-
if appdata
|
|
36
|
-
claude_dir = Path(appdata) / ".claude"
|
|
37
|
-
else:
|
|
38
|
-
claude_dir = Path.home() / "AppData" / "Roaming" / ".claude"
|
|
45
|
+
claude_dir = Path(appdata) / ".claude" if appdata else Path.home() / "AppData" / "Roaming" / ".claude"
|
|
39
46
|
else:
|
|
40
47
|
claude_dir = Path.home() / ".claude"
|
|
41
48
|
|
|
@@ -43,30 +50,20 @@ def _write_global_claude_md(home_path: Path, agent_name: str) -> Optional[Path]:
|
|
|
43
50
|
claude_dir.mkdir(parents=True, exist_ok=True)
|
|
44
51
|
claude_md = claude_dir / "CLAUDE.md"
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"skcapstone status # full pillar status\n"
|
|
61
|
-
"skcapstone memory list # recent memories\n"
|
|
62
|
-
"skcapstone sync push # push state to peers\n"
|
|
63
|
-
"skcapstone context show --format claude-md # regenerate this file\n"
|
|
64
|
-
"skcapstone sync pair --export-pubkey # export your GPG pubkey\n"
|
|
65
|
-
"skcapstone sync pair --import-pubkey <f> # import a peer's pubkey\n"
|
|
66
|
-
"```\n\n"
|
|
67
|
-
"> Auto-generated by `skcapstone init`. "
|
|
68
|
-
"Regenerate with: `skcapstone context generate --target claude-md`\n"
|
|
69
|
-
)
|
|
53
|
+
template_path = _get_claude_template_dir() / "CLAUDE.md"
|
|
54
|
+
if template_path.exists():
|
|
55
|
+
content = template_path.read_text(encoding="utf-8")
|
|
56
|
+
content = content.replace("{{AGENT_NAME}}", agent_name)
|
|
57
|
+
else:
|
|
58
|
+
# Minimal fallback if template is missing
|
|
59
|
+
content = (
|
|
60
|
+
f"# Claude Code — Global Agent Instructions ({agent_name})\n\n"
|
|
61
|
+
f"- **Agent**: `{agent_name}`\n"
|
|
62
|
+
f"- **Home**: `{home_path}`\n"
|
|
63
|
+
f"- **Env**: `SKCAPSTONE_AGENT={agent_name}`\n\n"
|
|
64
|
+
"Hooks auto-inject on SessionStart: soul + FEB chain + memories.\n\n"
|
|
65
|
+
"> Regenerate with: `skcapstone context generate --target claude-md`\n"
|
|
66
|
+
)
|
|
70
67
|
|
|
71
68
|
claude_md.write_text(content, encoding="utf-8")
|
|
72
69
|
return claude_md
|
|
@@ -74,6 +71,76 @@ def _write_global_claude_md(home_path: Path, agent_name: str) -> Optional[Path]:
|
|
|
74
71
|
return None
|
|
75
72
|
|
|
76
73
|
|
|
74
|
+
def _write_claude_settings(merge: bool = True) -> Optional[Path]:
|
|
75
|
+
"""Write (or merge) ~/.claude/settings.json with SK hook registrations.
|
|
76
|
+
|
|
77
|
+
Uses the bundled defaults/claude/settings.json template, substituting
|
|
78
|
+
{{SKMEMORY_HOOKS_DIR}} with the real skmemory hooks path.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
merge: If True and settings.json already exists, merge hooks rather
|
|
82
|
+
than overwrite. Default True.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Path to the written settings.json, or None on failure.
|
|
86
|
+
"""
|
|
87
|
+
import platform
|
|
88
|
+
|
|
89
|
+
if platform.system() == "Windows":
|
|
90
|
+
appdata = os.environ.get("APPDATA", "")
|
|
91
|
+
claude_dir = Path(appdata) / ".claude" if appdata else Path.home() / "AppData" / "Roaming" / ".claude"
|
|
92
|
+
else:
|
|
93
|
+
claude_dir = Path.home() / ".claude"
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
import skmemory
|
|
97
|
+
hooks_dir = str(Path(skmemory.__file__).parent / "hooks")
|
|
98
|
+
except ImportError:
|
|
99
|
+
return None # skmemory not installed — caller should use skmemory register instead
|
|
100
|
+
|
|
101
|
+
template_path = _get_claude_template_dir() / "settings.json"
|
|
102
|
+
if not template_path.exists():
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
raw = template_path.read_text(encoding="utf-8")
|
|
106
|
+
raw = raw.replace("{{SKMEMORY_HOOKS_DIR}}", hooks_dir)
|
|
107
|
+
new_settings = json.loads(raw)
|
|
108
|
+
|
|
109
|
+
settings_path = claude_dir / "settings.json"
|
|
110
|
+
if merge and settings_path.exists():
|
|
111
|
+
try:
|
|
112
|
+
existing = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
113
|
+
except (json.JSONDecodeError, OSError):
|
|
114
|
+
existing = {}
|
|
115
|
+
|
|
116
|
+
# Merge hooks: add new hooks that aren't already registered
|
|
117
|
+
existing_hooks = existing.get("hooks", {})
|
|
118
|
+
for event, hook_groups in new_settings.get("hooks", {}).items():
|
|
119
|
+
existing_event = existing_hooks.setdefault(event, [])
|
|
120
|
+
existing_commands = {
|
|
121
|
+
h.get("command")
|
|
122
|
+
for group in existing_event
|
|
123
|
+
for h in group.get("hooks", [])
|
|
124
|
+
if "command" in h
|
|
125
|
+
}
|
|
126
|
+
for group in hook_groups:
|
|
127
|
+
cmds = {h.get("command") for h in group.get("hooks", []) if "command" in h}
|
|
128
|
+
if not cmds.issubset(existing_commands):
|
|
129
|
+
existing_event.append(group)
|
|
130
|
+
existing["hooks"] = existing_hooks
|
|
131
|
+
# Preserve non-hook keys from template (skipDangerousModePermissionPrompt, etc.)
|
|
132
|
+
for k, v in new_settings.items():
|
|
133
|
+
if k != "hooks":
|
|
134
|
+
existing.setdefault(k, v)
|
|
135
|
+
final = existing
|
|
136
|
+
else:
|
|
137
|
+
claude_dir.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
final = new_settings
|
|
139
|
+
|
|
140
|
+
settings_path.write_text(json.dumps(final, indent=2), encoding="utf-8")
|
|
141
|
+
return settings_path
|
|
142
|
+
|
|
143
|
+
|
|
77
144
|
def register_setup_commands(main: click.Group) -> None:
|
|
78
145
|
"""Register all setup/lifecycle commands on the main CLI group."""
|
|
79
146
|
|
|
@@ -2,13 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import sys
|
|
6
|
+
from importlib.resources import as_file, files
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
5
9
|
import click
|
|
6
10
|
|
|
7
11
|
from ._common import AGENT_HOME
|
|
8
12
|
|
|
9
13
|
|
|
14
|
+
def _picker_path() -> Path:
|
|
15
|
+
"""Resolve the absolute path to the bundled sk-agent-picker.sh.
|
|
16
|
+
|
|
17
|
+
Works for any install layout (PyPI wheel, editable, install.sh) by
|
|
18
|
+
going through importlib.resources, so the picker is always sourced
|
|
19
|
+
from inside the installed skcapstone package.
|
|
20
|
+
"""
|
|
21
|
+
resource = files("skcapstone") / "data" / "sk-agent-picker.sh"
|
|
22
|
+
with as_file(resource) as p:
|
|
23
|
+
return Path(p)
|
|
24
|
+
|
|
25
|
+
|
|
10
26
|
def register_shell_commands(main: click.Group) -> None:
|
|
11
|
-
"""Register the 'shell'
|
|
27
|
+
"""Register the 'shell', 'shell-init', and 'shell-picker-path' commands."""
|
|
12
28
|
|
|
13
29
|
@main.command("shell")
|
|
14
30
|
@click.option(
|
|
@@ -41,3 +57,39 @@ def register_shell_commands(main: click.Group) -> None:
|
|
|
41
57
|
from ..shell import run_shell
|
|
42
58
|
|
|
43
59
|
run_shell(home=home)
|
|
60
|
+
|
|
61
|
+
@main.command("shell-init")
|
|
62
|
+
def shell_init_cmd() -> None:
|
|
63
|
+
"""Emit shell code that loads the SK agent picker.
|
|
64
|
+
|
|
65
|
+
Add to your ~/.bashrc (or ~/.zshrc):
|
|
66
|
+
|
|
67
|
+
eval "$(skcapstone shell-init)"
|
|
68
|
+
|
|
69
|
+
This sources the picker shipped inside the skcapstone package,
|
|
70
|
+
so a single `pip install skcapstone` (PyPI, editable, or via
|
|
71
|
+
install.sh) is enough — no external script copy required.
|
|
72
|
+
"""
|
|
73
|
+
from .. import DEFAULT_AGENT
|
|
74
|
+
|
|
75
|
+
path = _picker_path()
|
|
76
|
+
if not path.is_file():
|
|
77
|
+
click.echo(f"# skcapstone: picker missing at {path}", err=True)
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
# Propagate the canonical default agent (single source of truth lives
|
|
80
|
+
# in skcapstone.DEFAULT_AGENT) so the picker and every child process
|
|
81
|
+
# inherit one authoritative value without re-hardcoding it in shell.
|
|
82
|
+
click.echo(f'export SK_DEFAULT_AGENT="{DEFAULT_AGENT}"')
|
|
83
|
+
click.echo(f'source "{path}"')
|
|
84
|
+
|
|
85
|
+
@main.command("shell-picker-path")
|
|
86
|
+
def shell_picker_path_cmd() -> None:
|
|
87
|
+
"""Print the absolute path to the bundled sk-agent-picker.sh.
|
|
88
|
+
|
|
89
|
+
Useful for hand-rolled shell wiring, debugging, or scripted
|
|
90
|
+
invocation of the picker.
|
|
91
|
+
"""
|
|
92
|
+
path = _picker_path()
|
|
93
|
+
click.echo(str(path))
|
|
94
|
+
if not path.is_file():
|
|
95
|
+
sys.exit(1)
|
|
@@ -114,8 +114,8 @@ def register_skills_commands(main: click.Group) -> None:
|
|
|
114
114
|
try:
|
|
115
115
|
skill_entries = client.search(query) if query else client.list_skills()
|
|
116
116
|
source = "remote"
|
|
117
|
-
except Exception:
|
|
118
|
-
|
|
117
|
+
except Exception as exc:
|
|
118
|
+
logger.warning("Registry client query failed, falling back: %s", exc)
|
|
119
119
|
|
|
120
120
|
# 2. Try GitHub raw catalog (always fresh, no server needed)
|
|
121
121
|
if skill_entries is None and not offline:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import logging
|
|
6
7
|
import shutil
|
|
7
8
|
import sys
|
|
8
9
|
from datetime import datetime, timezone
|
|
@@ -18,6 +19,8 @@ from .. import SKCAPSTONE_AGENT
|
|
|
18
19
|
from rich.panel import Panel
|
|
19
20
|
from rich.table import Table
|
|
20
21
|
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
21
24
|
# Path to the soul-blueprints repository (community blueprints)
|
|
22
25
|
_BLUEPRINTS_REPO = Path.home() / "clawd" / "soul-blueprints" / "blueprints"
|
|
23
26
|
|
|
@@ -61,9 +64,9 @@ def register_soul_commands(main: click.Group) -> None:
|
|
|
61
64
|
"""Reusable --agent/-a option for soul subcommands."""
|
|
62
65
|
return click.option(
|
|
63
66
|
"--agent", "-a",
|
|
64
|
-
default=SKCAPSTONE_AGENT
|
|
65
|
-
envvar="
|
|
66
|
-
help="Agent profile name (default:
|
|
67
|
+
default=SKCAPSTONE_AGENT,
|
|
68
|
+
envvar="SKAGENT",
|
|
69
|
+
help="Agent profile name (default: SKAGENT or active agent).",
|
|
67
70
|
)
|
|
68
71
|
|
|
69
72
|
@main.group()
|
|
@@ -332,8 +335,8 @@ def register_soul_commands(main: click.Group) -> None:
|
|
|
332
335
|
import json
|
|
333
336
|
base_data = json.loads(base_path.read_text(encoding="utf-8"))
|
|
334
337
|
vibe = base_data.get("vibe", "")
|
|
335
|
-
except Exception:
|
|
336
|
-
|
|
338
|
+
except Exception as exc:
|
|
339
|
+
logger.warning("Failed to read vibe from soul base.json: %s", exc)
|
|
337
340
|
|
|
338
341
|
lines = [
|
|
339
342
|
f"Agent: [bold magenta]{agent}[/]",
|