@smilintux/skcapstone 0.10.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 +123 -30
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +7 -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.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
- package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
- package/launchd/install-launchd.sh +6 -6
- 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 +7 -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 +159 -5
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/nvidia-proxy.mjs +78 -26
- 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 +3 -3
- package/scripts/telegram-catchup-all.sh +12 -1
- 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/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 +11 -7
- 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 +132 -77
- 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 +14 -12
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +1 -1
- 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 +19 -19
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +83 -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 +875 -121
- 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 +55 -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
|
@@ -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}[/]",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import logging
|
|
6
7
|
import os
|
|
7
8
|
import shutil
|
|
8
9
|
import sys
|
|
@@ -19,6 +20,8 @@ from ..runtime import get_runtime
|
|
|
19
20
|
from rich.panel import Panel
|
|
20
21
|
from rich.table import Table
|
|
21
22
|
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
def _probe_llm_backends() -> dict[str, bool]:
|
|
24
27
|
"""Probe LLM backend availability.
|
|
@@ -43,8 +46,8 @@ def _probe_llm_backends() -> dict[str, bool]:
|
|
|
43
46
|
urllib.request.Request(f"{host}/api/tags"), timeout=2
|
|
44
47
|
):
|
|
45
48
|
backends["ollama"] = True
|
|
46
|
-
except Exception:
|
|
47
|
-
|
|
49
|
+
except Exception as exc:
|
|
50
|
+
logger.debug("Ollama probe failed (not available): %s", exc)
|
|
48
51
|
return backends
|
|
49
52
|
|
|
50
53
|
|
|
@@ -129,8 +132,8 @@ def _read_local_heartbeat(home: Path) -> Optional[dict]:
|
|
|
129
132
|
hb_path = home / "heartbeats" / f"{agent_name}.json"
|
|
130
133
|
if hb_path.exists():
|
|
131
134
|
return json.loads(hb_path.read_text())
|
|
132
|
-
except Exception:
|
|
133
|
-
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
logger.warning("Failed to load heartbeat data: %s", exc)
|
|
134
137
|
return None
|
|
135
138
|
|
|
136
139
|
|
|
@@ -148,8 +151,11 @@ def _print_consciousness_metrics(console, home: Optional[Path] = None) -> None:
|
|
|
148
151
|
import urllib.request
|
|
149
152
|
import urllib.error
|
|
150
153
|
|
|
154
|
+
from .. import AGENT_PORTS, DEFAULT_PORT, SKCAPSTONE_AGENT
|
|
155
|
+
|
|
156
|
+
port = AGENT_PORTS.get(SKCAPSTONE_AGENT, DEFAULT_PORT)
|
|
151
157
|
try:
|
|
152
|
-
with urllib.request.urlopen("http://localhost:
|
|
158
|
+
with urllib.request.urlopen(f"http://localhost:{port}/consciousness", timeout=2) as resp:
|
|
153
159
|
data = json.loads(resp.read().decode("utf-8"))
|
|
154
160
|
enabled = data.get("enabled", False)
|
|
155
161
|
messages = data.get("messages_processed", 0)
|
|
@@ -250,6 +256,14 @@ def register_status_commands(main: click.Group) -> None:
|
|
|
250
256
|
trust_detail += " [green]ENTANGLED[/]"
|
|
251
257
|
table.add_row("Trust", "Cloud 9", status_icon(trust.status), trust_detail)
|
|
252
258
|
|
|
259
|
+
con = m.consciousness
|
|
260
|
+
con_detail = f"{con.sessions_digested} sessions digested, {con.topics_tracked} topics"
|
|
261
|
+
if con.whisper_active:
|
|
262
|
+
con_detail += " [green]daemon active[/]"
|
|
263
|
+
if con.whisper_md_age_hours < 24:
|
|
264
|
+
con_detail += f" [dim](whisper {con.whisper_md_age_hours:.1f}h old)[/]"
|
|
265
|
+
table.add_row("Consciousness", "SKWhisper", status_icon(con.status), con_detail)
|
|
266
|
+
|
|
253
267
|
sec = m.security
|
|
254
268
|
table.add_row(
|
|
255
269
|
"Security", "SKSecurity", status_icon(sec.status),
|
|
@@ -332,8 +346,8 @@ def register_status_commands(main: click.Group) -> None:
|
|
|
332
346
|
f"\n [bold yellow]WARNING:[/] [yellow]Low disk space: "
|
|
333
347
|
f"{free_gb:.1f} GB free[/]"
|
|
334
348
|
)
|
|
335
|
-
except Exception:
|
|
336
|
-
|
|
349
|
+
except Exception as exc:
|
|
350
|
+
logger.debug("Failed to check disk space: %s", exc)
|
|
337
351
|
|
|
338
352
|
console.print()
|
|
339
353
|
console.print(f" [dim]Home: {m.home}[/]")
|
|
@@ -575,11 +589,23 @@ def register_status_commands(main: click.Group) -> None:
|
|
|
575
589
|
"agent": "Agent Home",
|
|
576
590
|
"identity": "Identity (CapAuth)",
|
|
577
591
|
"memory": "Memory (SKMemory)",
|
|
578
|
-
"transport": "Transport (
|
|
592
|
+
"transport": "Transport (SKComms)",
|
|
579
593
|
"sync": "Sync (Singularity)",
|
|
594
|
+
"codex": "Codex Integration",
|
|
595
|
+
"harness": "AI Harness (Claude Code)",
|
|
580
596
|
}
|
|
581
597
|
|
|
582
|
-
for cat_key in [
|
|
598
|
+
for cat_key in [
|
|
599
|
+
"packages",
|
|
600
|
+
"system",
|
|
601
|
+
"agent",
|
|
602
|
+
"identity",
|
|
603
|
+
"memory",
|
|
604
|
+
"transport",
|
|
605
|
+
"sync",
|
|
606
|
+
"codex",
|
|
607
|
+
"harness",
|
|
608
|
+
]:
|
|
583
609
|
checks = categories.get(cat_key, [])
|
|
584
610
|
if not checks:
|
|
585
611
|
continue
|
|
@@ -718,8 +744,8 @@ def register_status_commands(main: click.Group) -> None:
|
|
|
718
744
|
import webbrowser
|
|
719
745
|
try:
|
|
720
746
|
webbrowser.open(url)
|
|
721
|
-
except Exception:
|
|
722
|
-
|
|
747
|
+
except Exception as exc:
|
|
748
|
+
logger.warning("Failed to open browser for dashboard: %s", exc)
|
|
723
749
|
|
|
724
750
|
server = start_dashboard(home_path, port=port)
|
|
725
751
|
try:
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
|
+
import os
|
|
7
8
|
import sys
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
@@ -166,6 +167,26 @@ def register_telegram_commands(main: click.Group) -> None:
|
|
|
166
167
|
lines.append(f" {key}: [cyan]{val}[/]")
|
|
167
168
|
|
|
168
169
|
console.print(Panel("\n".join(lines), title="Telegram Catch-Up", border_style="green"))
|
|
170
|
+
|
|
171
|
+
# Post-import hook: run auto-tagger on the last 6 minutes of files
|
|
172
|
+
# (just enough to cover what the catchup brought in). Fires
|
|
173
|
+
# inline so failures are visible; the hourly cron is the safety net.
|
|
174
|
+
_auto_tag_script = os.path.expanduser(
|
|
175
|
+
"~/.skcapstone/agents/lumina/scripts/auto-tag-hallucinations.py"
|
|
176
|
+
)
|
|
177
|
+
if os.path.isfile(_auto_tag_script):
|
|
178
|
+
import subprocess as _subprocess
|
|
179
|
+
_tag_result = _subprocess.run(
|
|
180
|
+
[sys.executable, _auto_tag_script, "--hours", "0.1", "--quiet"],
|
|
181
|
+
capture_output=True,
|
|
182
|
+
text=True,
|
|
183
|
+
)
|
|
184
|
+
if _tag_result.returncode != 0:
|
|
185
|
+
console.print(
|
|
186
|
+
f"[yellow]auto-tag-hallucinations warning:[/] {_tag_result.stderr.strip() or 'non-zero exit'}"
|
|
187
|
+
)
|
|
188
|
+
elif _tag_result.stdout.strip():
|
|
189
|
+
console.print(f"[dim]auto-tag:[/] {_tag_result.stdout.strip()}")
|
|
169
190
|
except Exception as e:
|
|
170
191
|
console.print(f"[red]Error:[/] {e}")
|
|
171
192
|
raise SystemExit(1)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Usage:
|
|
4
4
|
skcapstone test # run all packages
|
|
5
|
-
skcapstone test --package
|
|
5
|
+
skcapstone test --package skcomms # run one package
|
|
6
6
|
skcapstone test --fast # stop on first failure
|
|
7
7
|
skcapstone test --json-out # machine-readable JSON output
|
|
8
8
|
skcapstone test --verbose # pass -v to pytest
|
|
@@ -183,15 +183,15 @@ def register_test_commands(main: click.Group) -> None:
|
|
|
183
183
|
) -> None:
|
|
184
184
|
"""Run pytest across all ecosystem packages and show a summary table.
|
|
185
185
|
|
|
186
|
-
Discovers packages in the monorepo (skcapstone, capauth,
|
|
187
|
-
skchat, skmemory, cloud9
|
|
186
|
+
Discovers packages in the monorepo (skcapstone, capauth, skcomms,
|
|
187
|
+
skchat, skmemory, cloud9) and runs their test suites in
|
|
188
188
|
sequence, then renders a consolidated Rich table.
|
|
189
189
|
|
|
190
190
|
\b
|
|
191
191
|
Examples:
|
|
192
192
|
skcapstone test
|
|
193
|
-
skcapstone test --package
|
|
194
|
-
skcapstone test -p
|
|
193
|
+
skcapstone test --package skcomms
|
|
194
|
+
skcapstone test -p skcomms -p skchat
|
|
195
195
|
skcapstone test --fast --verbose
|
|
196
196
|
skcapstone test --json-out | jq .
|
|
197
197
|
"""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""test-connection command — ping a peer via
|
|
1
|
+
"""test-connection command — ping a peer via SKComms and measure latency.
|
|
2
2
|
|
|
3
3
|
Usage:
|
|
4
4
|
skcapstone test-connection <peer>
|
|
@@ -176,7 +176,7 @@ def register_test_connection_commands(main: click.Group) -> None:
|
|
|
176
176
|
)
|
|
177
177
|
@click.option("--home", default=AGENT_HOME, type=click.Path())
|
|
178
178
|
def test_connection(peer: str, timeout: float, count: int, home: str) -> None:
|
|
179
|
-
"""Test connectivity to a peer by sending a ping via
|
|
179
|
+
"""Test connectivity to a peer by sending a ping via SKComms.
|
|
180
180
|
|
|
181
181
|
Sends a ping message, waits for the peer to reply with a pong,
|
|
182
182
|
and reports the round-trip latency. Exits with code 1 when the
|