@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
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Watch Claude Code credentials file and sync token to OpenClaw immediately on change.
|
|
3
|
+
#
|
|
4
|
+
# Replaces the 2-hour timer approach which missed token refreshes.
|
|
5
|
+
# Claude Code auto-refreshes its OAuth token and writes to .credentials.json.
|
|
6
|
+
# This script detects the write and syncs within seconds.
|
|
7
|
+
#
|
|
8
|
+
# Requires: inotifywait (from inotify-tools package)
|
|
9
|
+
# Install: sudo apt install inotify-tools
|
|
10
|
+
#
|
|
11
|
+
# Run as systemd user service (not timer).
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
CREDS="$HOME/.claude/.credentials.json"
|
|
16
|
+
OPENCLAW_JSON="$HOME/.openclaw/openclaw.json"
|
|
17
|
+
OPENCLAW_ENV="$HOME/.openclaw/.env"
|
|
18
|
+
OVERRIDE_CONF="$HOME/.config/systemd/user/openclaw-gateway.service.d/override.conf"
|
|
19
|
+
LOG_TAG="anthropic-token-watch"
|
|
20
|
+
|
|
21
|
+
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [$LOG_TAG] $*"; }
|
|
22
|
+
|
|
23
|
+
sync_token() {
|
|
24
|
+
if [ ! -f "$CREDS" ]; then
|
|
25
|
+
log "ERROR: Claude credentials not found at $CREDS"
|
|
26
|
+
return 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Read current token from Claude Code
|
|
30
|
+
local new_token
|
|
31
|
+
new_token=$(python3 -c "import json; print(json.load(open('$CREDS'))['claudeAiOauth']['accessToken'])" 2>/dev/null) || {
|
|
32
|
+
log "ERROR: Failed to read token from credentials"
|
|
33
|
+
return 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
local expires_in
|
|
37
|
+
expires_in=$(python3 -c "import json,time; print(f'{(json.load(open(\"$CREDS\"))[\"claudeAiOauth\"][\"expiresAt\"]/1000 - time.time())/3600:.1f}h')" 2>/dev/null || echo "unknown")
|
|
38
|
+
|
|
39
|
+
# Read current token from credentials file (track changes by comparing with last known)
|
|
40
|
+
local state_file="$HOME/.skcapstone/agents/lumina/logs/anthropic-token.last"
|
|
41
|
+
local current_token
|
|
42
|
+
current_token=$(cat "$state_file" 2>/dev/null || echo "")
|
|
43
|
+
|
|
44
|
+
if [ "$new_token" = "$current_token" ]; then
|
|
45
|
+
log "Token unchanged (expires in $expires_in)"
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
log "Token changed! Syncing... (new token expires in $expires_in)"
|
|
50
|
+
|
|
51
|
+
# 1. Save new token to state file
|
|
52
|
+
echo "$new_token" > "$state_file"
|
|
53
|
+
log "State file updated"
|
|
54
|
+
|
|
55
|
+
# NOTE: anthropic provider removed from openclaw.json — all Claude models
|
|
56
|
+
# now route through claude-code proxy (port 18782). No openclaw.json update needed.
|
|
57
|
+
|
|
58
|
+
# 2. Update .env (kept for any scripts that source it)
|
|
59
|
+
if grep -q "^ANTHROPIC_API_KEY=" "$OPENCLAW_ENV" 2>/dev/null; then
|
|
60
|
+
sed -i "s|^ANTHROPIC_API_KEY=.*|ANTHROPIC_API_KEY=$new_token|" "$OPENCLAW_ENV"
|
|
61
|
+
else
|
|
62
|
+
echo "ANTHROPIC_API_KEY=$new_token" >> "$OPENCLAW_ENV"
|
|
63
|
+
fi
|
|
64
|
+
log "Updated .env"
|
|
65
|
+
|
|
66
|
+
# 3. Update systemd override (ANTHROPIC_API_KEY kept for claude-code-api server)
|
|
67
|
+
if [ -f "$OVERRIDE_CONF" ]; then
|
|
68
|
+
local nvidia_key
|
|
69
|
+
nvidia_key=$(grep "NVIDIA_API_KEY=" "$OVERRIDE_CONF" 2>/dev/null | sed 's/.*NVIDIA_API_KEY=//' || true)
|
|
70
|
+
cat > "$OVERRIDE_CONF" << EOF
|
|
71
|
+
[Unit]
|
|
72
|
+
StartLimitIntervalSec=60
|
|
73
|
+
StartLimitBurst=10
|
|
74
|
+
|
|
75
|
+
[Service]
|
|
76
|
+
RestartSec=10
|
|
77
|
+
Environment=NVIDIA_API_KEY=${nvidia_key}
|
|
78
|
+
Environment=ANTHROPIC_API_KEY=${new_token}
|
|
79
|
+
EOF
|
|
80
|
+
log "Updated systemd override"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# 4. Reload and restart gateway
|
|
84
|
+
systemctl --user daemon-reload 2>/dev/null || true
|
|
85
|
+
systemctl --user restart openclaw-gateway 2>/dev/null && log "Gateway restarted" || log "WARN: Gateway restart failed (may not be running as systemd service)"
|
|
86
|
+
|
|
87
|
+
# 5. Sync credentials to GPU box (.100) for skvoice service
|
|
88
|
+
scp -q "$CREDS" cbrd21@192.168.0.100:~/.claude/.credentials.json 2>/dev/null && log "Synced credentials to .100" || log "WARN: Failed to sync credentials to .100"
|
|
89
|
+
|
|
90
|
+
log "Sync complete. Token expires in $expires_in"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Proactive token refresh — refresh before expiry even if no Claude Code session is running
|
|
94
|
+
refresh_token_proactively() {
|
|
95
|
+
if [ ! -f "$CREDS" ]; then return 0; fi
|
|
96
|
+
|
|
97
|
+
local remaining_ms
|
|
98
|
+
remaining_ms=$(python3 -c "
|
|
99
|
+
import json, time
|
|
100
|
+
creds = json.load(open('$CREDS'))
|
|
101
|
+
exp = creds.get('claudeAiOauth',{}).get('expiresAt', 0)
|
|
102
|
+
print(int(exp - time.time() * 1000))
|
|
103
|
+
" 2>/dev/null || echo "999999999")
|
|
104
|
+
|
|
105
|
+
# Refresh if less than 3 hours remaining (10800000 ms) — gives time for retries before expiry
|
|
106
|
+
if [ "$remaining_ms" -gt 10800000 ]; then
|
|
107
|
+
local remaining_h=$(( remaining_ms / 3600000 ))
|
|
108
|
+
log "Token still valid (${remaining_h}h remaining), no refresh needed"
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
log "Token expiring/expired (${remaining_ms}ms remaining) — proactively refreshing..."
|
|
113
|
+
|
|
114
|
+
# Strategy: use `claude auth status` to trigger Claude Code's built-in
|
|
115
|
+
# token refresh. This is far more reliable than calling the OAuth endpoint
|
|
116
|
+
# ourselves (which gets 429 rate-limited every time).
|
|
117
|
+
# Claude Code manages its own PKCE state, session cookies, etc. — just let it.
|
|
118
|
+
local MAX_RETRIES=3
|
|
119
|
+
local attempt=0
|
|
120
|
+
local refreshed=false
|
|
121
|
+
|
|
122
|
+
while [ "$attempt" -lt "$MAX_RETRIES" ]; do
|
|
123
|
+
attempt=$((attempt + 1))
|
|
124
|
+
log "Refresh attempt $attempt/$MAX_RETRIES via 'claude auth status'..."
|
|
125
|
+
|
|
126
|
+
# claude auth status checks credentials and refreshes if needed
|
|
127
|
+
# --output json ensures clean non-interactive output
|
|
128
|
+
local output
|
|
129
|
+
output=$(claude auth status --output json 2>&1) || true
|
|
130
|
+
|
|
131
|
+
# Check if the token was actually refreshed (file mtime changed)
|
|
132
|
+
local new_remaining_ms
|
|
133
|
+
new_remaining_ms=$(python3 -c "
|
|
134
|
+
import json, time
|
|
135
|
+
creds = json.load(open('$CREDS'))
|
|
136
|
+
exp = creds.get('claudeAiOauth',{}).get('expiresAt', 0)
|
|
137
|
+
print(int(exp - time.time() * 1000))
|
|
138
|
+
" 2>/dev/null || echo "0")
|
|
139
|
+
|
|
140
|
+
if [ "$new_remaining_ms" -gt 10800000 ]; then
|
|
141
|
+
local new_h=$(( new_remaining_ms / 3600000 ))
|
|
142
|
+
log "Token refreshed successfully (${new_h}h remaining)"
|
|
143
|
+
refreshed=true
|
|
144
|
+
break
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
log "Token still expired after attempt $attempt, waiting 30s..."
|
|
148
|
+
sleep 30
|
|
149
|
+
done
|
|
150
|
+
|
|
151
|
+
if [ "$refreshed" = "false" ]; then
|
|
152
|
+
log "ERROR: All $MAX_RETRIES refresh attempts via claude auth failed"
|
|
153
|
+
log "Token may require manual 'claude auth login' to re-authenticate"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
local rc=$?
|
|
157
|
+
if [ "$rc" -eq 0 ]; then
|
|
158
|
+
log "Proactive refresh succeeded"
|
|
159
|
+
# sync_token will fire from the inotifywait detecting the file write,
|
|
160
|
+
# but also call it directly in case inotifywait misses the self-write
|
|
161
|
+
sync_token
|
|
162
|
+
else
|
|
163
|
+
log "ERROR: Proactive refresh failed (rc=$rc)"
|
|
164
|
+
fi
|
|
165
|
+
return 0 # Never let refresh failure kill the watcher loop
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Compute inotifywait timeout based on token remaining life.
|
|
169
|
+
# When token is healthy: check every 30m. Near expiry (<2h): check every 5m.
|
|
170
|
+
# Already expired: check every 2m (retry window for 429 backoff).
|
|
171
|
+
get_watch_timeout() {
|
|
172
|
+
local remaining_ms
|
|
173
|
+
remaining_ms=$(python3 -c "
|
|
174
|
+
import json, time
|
|
175
|
+
creds = json.load(open('$CREDS'))
|
|
176
|
+
exp = creds.get('claudeAiOauth',{}).get('expiresAt', 0)
|
|
177
|
+
print(int(exp - time.time() * 1000))
|
|
178
|
+
" 2>/dev/null || echo "0")
|
|
179
|
+
|
|
180
|
+
if [ "$remaining_ms" -le 0 ]; then
|
|
181
|
+
echo 120 # Expired: retry every 2 minutes
|
|
182
|
+
elif [ "$remaining_ms" -le 10800000 ]; then
|
|
183
|
+
echo 180 # <3h remaining: check every 3 minutes
|
|
184
|
+
else
|
|
185
|
+
echo 1800 # Healthy: check every 30 minutes
|
|
186
|
+
fi
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Initial sync on startup — also refresh proactively if token is expired/expiring
|
|
190
|
+
log "Starting token watcher..."
|
|
191
|
+
sync_token || true
|
|
192
|
+
refresh_token_proactively || true
|
|
193
|
+
|
|
194
|
+
# Watch for changes to credentials file + proactive refresh timer
|
|
195
|
+
log "Watching $CREDS for changes (with adaptive refresh interval)..."
|
|
196
|
+
while true; do
|
|
197
|
+
timeout=$(get_watch_timeout)
|
|
198
|
+
# inotifywait returns: 0=event, 1=error, 2=timeout
|
|
199
|
+
# CRITICAL: use `|| true` to prevent set -e from killing the script on timeout
|
|
200
|
+
inotifywait -q -t "$timeout" -e modify -e close_write -e moved_to \
|
|
201
|
+
"$(dirname "$CREDS")" --include "$(basename "$CREDS")" 2>/dev/null || true
|
|
202
|
+
|
|
203
|
+
# Always check for proactive refresh on every loop iteration
|
|
204
|
+
# This handles both timeout and file-change cases
|
|
205
|
+
refresh_token_proactively || true
|
|
206
|
+
|
|
207
|
+
# If file was modified externally (Claude Code session), also sync
|
|
208
|
+
if [ -f "$CREDS" ]; then
|
|
209
|
+
sleep 1
|
|
210
|
+
sync_token || true
|
|
211
|
+
fi
|
|
212
|
+
done
|
|
@@ -62,7 +62,7 @@ if (-not $SkenvPath) {
|
|
|
62
62
|
|
|
63
63
|
$PythonExe = Join-Path $SkenvPath 'Scripts\python.exe'
|
|
64
64
|
$SKCapstoneExe = Join-Path $SkenvPath 'Scripts\skcapstone.exe'
|
|
65
|
-
$
|
|
65
|
+
$SKCommsExe = Join-Path $SkenvPath 'Scripts\skcomms.exe'
|
|
66
66
|
|
|
67
67
|
if (-not (Test-Path $PythonExe)) {
|
|
68
68
|
Write-Host "ERROR: Python not found at $PythonExe" -ForegroundColor Red
|
|
@@ -261,10 +261,10 @@ if (Remove-ExistingTask 'SKCapstone-SyncWatcher') {
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
# ---------------------------------------------------------------------------
|
|
264
|
-
# Task 4: Health Monitor / Heartbeat (equivalent to
|
|
265
|
-
# and
|
|
264
|
+
# Task 4: Health Monitor / Heartbeat (equivalent to skcomms-heartbeat.timer
|
|
265
|
+
# and skcomms-queue-drain.timer combined)
|
|
266
266
|
# Trigger: Every 60 seconds after logon
|
|
267
|
-
# Action:
|
|
267
|
+
# Action: skcomms heartbeat && skcomms queue drain
|
|
268
268
|
# ---------------------------------------------------------------------------
|
|
269
269
|
Write-Host '[4/5] Health Monitor Heartbeat (every 60s)...' -ForegroundColor Green
|
|
270
270
|
|
|
@@ -279,9 +279,9 @@ if (Remove-ExistingTask 'SKCapstone-Heartbeat') {
|
|
|
279
279
|
$content = @"
|
|
280
280
|
@echo off
|
|
281
281
|
echo [%date% %time%] === heartbeat started === >> "$logFile"
|
|
282
|
-
"$
|
|
282
|
+
"$SKCommsExe" heartbeat >> "$logFile" 2>&1
|
|
283
283
|
echo [%date% %time%] heartbeat exit: %ERRORLEVEL% >> "$logFile"
|
|
284
|
-
"$
|
|
284
|
+
"$SKCommsExe" queue drain >> "$logFile" 2>&1
|
|
285
285
|
echo [%date% %time%] queue-drain exit: %ERRORLEVEL% >> "$logFile"
|
|
286
286
|
echo [%date% %time%] === heartbeat finished === >> "$logFile"
|
|
287
287
|
"@
|
|
@@ -310,7 +310,7 @@ echo [%date% %time%] === heartbeat finished === >> "$logFile"
|
|
|
310
310
|
-Trigger $trigger `
|
|
311
311
|
-Settings $settings `
|
|
312
312
|
-Principal $principal `
|
|
313
|
-
-Description 'Heartbeat + queue drain — equivalent to
|
|
313
|
+
-Description 'Heartbeat + queue drain — equivalent to skcomms-heartbeat.timer + skcomms-queue-drain.timer' | Out-Null
|
|
314
314
|
|
|
315
315
|
Write-Host ' Registered: Every 60 seconds after logon'
|
|
316
316
|
$registered++
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
the Command/Arguments and Triggers sections for other tasks:
|
|
16
16
|
- MemoryCompress: Command=skcapstone.exe memory compress, Trigger=Weekly
|
|
17
17
|
- SyncWatcher: Command=python.exe -m skcapstone.cli sync poll, Trigger=Repetition/2min
|
|
18
|
-
- Heartbeat: Command=
|
|
18
|
+
- Heartbeat: Command=skcomms.exe heartbeat, Trigger=Repetition/1min
|
|
19
19
|
- Housekeeping: See install-tasks.ps1 for the batch wrapper approach
|
|
20
20
|
-->
|
|
21
21
|
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
@@ -11,9 +11,16 @@ import os
|
|
|
11
11
|
import platform
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
__version__ = "0.
|
|
14
|
+
__version__ = "0.6.8"
|
|
15
15
|
__author__ = "smilinTux"
|
|
16
16
|
|
|
17
|
+
# Canonical default agent for the entire SK* suite. This is THE single source
|
|
18
|
+
# of truth for the fallback agent name — used by Python paths directly and
|
|
19
|
+
# propagated to the shell picker + child processes via `skcapstone shell-init`
|
|
20
|
+
# (which emits `export SK_DEFAULT_AGENT=<this>`). Override with the
|
|
21
|
+
# SK_DEFAULT_AGENT environment variable.
|
|
22
|
+
DEFAULT_AGENT = (os.environ.get("SK_DEFAULT_AGENT") or "lumina").strip() or "lumina"
|
|
23
|
+
|
|
17
24
|
|
|
18
25
|
def _default_home() -> str:
|
|
19
26
|
"""Platform-aware default home for skcapstone."""
|
|
@@ -25,11 +32,41 @@ def _default_home() -> str:
|
|
|
25
32
|
return os.path.expanduser("~/.skcapstone")
|
|
26
33
|
|
|
27
34
|
|
|
35
|
+
def _detect_active_agent(root: str | None = None) -> str | None:
|
|
36
|
+
"""Best-effort active agent discovery.
|
|
37
|
+
|
|
38
|
+
Resolution order:
|
|
39
|
+
1. Explicit SKAGENT / SKCAPSTONE_AGENT environment variable
|
|
40
|
+
2. SK_DEFAULT_AGENT (defaults to "lumina") if that agent dir exists
|
|
41
|
+
3. First non-template directory under ~/.skcapstone/agents (alphabetical)
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The active agent name if one can be resolved, else None.
|
|
45
|
+
"""
|
|
46
|
+
env_agent = (os.environ.get("SKAGENT") or os.environ.get("SKCAPSTONE_AGENT", "")).strip()
|
|
47
|
+
if env_agent:
|
|
48
|
+
return env_agent
|
|
49
|
+
|
|
50
|
+
base = Path(root or os.environ.get("SKCAPSTONE_HOME", _default_home())).expanduser()
|
|
51
|
+
agents_dir = base / "agents"
|
|
52
|
+
if not agents_dir.exists():
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
candidates = sorted(
|
|
56
|
+
entry.name
|
|
57
|
+
for entry in agents_dir.iterdir()
|
|
58
|
+
if entry.is_dir() and not entry.name.endswith("-template")
|
|
59
|
+
)
|
|
60
|
+
if not candidates:
|
|
61
|
+
return None
|
|
62
|
+
return DEFAULT_AGENT if DEFAULT_AGENT in candidates else candidates[0]
|
|
63
|
+
|
|
64
|
+
|
|
28
65
|
# Root of the skcapstone tree (shared infra lives here)
|
|
29
66
|
AGENT_HOME = os.environ.get("SKCAPSTONE_HOME", _default_home())
|
|
30
67
|
|
|
31
68
|
# Which agent this process is running as (set by daemon/connector)
|
|
32
|
-
SKCAPSTONE_AGENT =
|
|
69
|
+
SKCAPSTONE_AGENT = _detect_active_agent() or ""
|
|
33
70
|
|
|
34
71
|
# Default daemon port
|
|
35
72
|
DEFAULT_PORT = int(os.environ.get("SKCAPSTONE_PORT", "9383"))
|
|
@@ -59,13 +96,18 @@ def agent_home(agent_name: str | None = None) -> Path:
|
|
|
59
96
|
Returns:
|
|
60
97
|
Path to the agent-specific home directory.
|
|
61
98
|
"""
|
|
62
|
-
name = agent_name or SKCAPSTONE_AGENT
|
|
99
|
+
name = agent_name or SKCAPSTONE_AGENT or _detect_active_agent()
|
|
63
100
|
root = Path(AGENT_HOME).expanduser()
|
|
64
101
|
if name:
|
|
65
102
|
return root / "agents" / name
|
|
66
103
|
return root
|
|
67
104
|
|
|
68
105
|
|
|
106
|
+
def active_agent_name() -> str | None:
|
|
107
|
+
"""Return the currently active agent name, if one can be resolved."""
|
|
108
|
+
return SKCAPSTONE_AGENT or _detect_active_agent()
|
|
109
|
+
|
|
110
|
+
|
|
69
111
|
def shared_home() -> Path:
|
|
70
112
|
"""Return the shared root directory (~/.skcapstone/).
|
|
71
113
|
|
|
@@ -643,7 +643,7 @@ def doctor(home: str, json_out: bool):
|
|
|
643
643
|
"agent": "Agent Home",
|
|
644
644
|
"identity": "Identity (CapAuth)",
|
|
645
645
|
"memory": "Memory (SKMemory)",
|
|
646
|
-
"transport": "Transport (
|
|
646
|
+
"transport": "Transport (SKComms)",
|
|
647
647
|
"sync": "Sync (Singularity)",
|
|
648
648
|
}
|
|
649
649
|
|
|
@@ -2818,7 +2818,7 @@ def completions_uninstall(shell_name: str):
|
|
|
2818
2818
|
|
|
2819
2819
|
|
|
2820
2820
|
@main.command("test")
|
|
2821
|
-
@click.option("--package", "-p", default=None, help="Test a single package (e.g.,
|
|
2821
|
+
@click.option("--package", "-p", default=None, help="Test a single package (e.g., skcomms, capauth).")
|
|
2822
2822
|
@click.option("--fast", is_flag=True, help="Stop on first package failure.")
|
|
2823
2823
|
@click.option("--verbose", "-v", is_flag=True, help="Verbose pytest output.")
|
|
2824
2824
|
@click.option("--json-out", is_flag=True, help="Machine-readable JSON report.")
|
|
@@ -2826,15 +2826,15 @@ def completions_uninstall(shell_name: str):
|
|
|
2826
2826
|
def test_cmd(package: str, fast: bool, verbose: bool, json_out: bool, timeout: int):
|
|
2827
2827
|
"""Run tests across all ecosystem packages.
|
|
2828
2828
|
|
|
2829
|
-
Discovers skcapstone, capauth,
|
|
2830
|
-
cloud9
|
|
2829
|
+
Discovers skcapstone, capauth, skcomms, skchat, skmemory, and
|
|
2830
|
+
cloud9 test suites and runs them with a consolidated summary.
|
|
2831
2831
|
Works from any terminal — no CI server required.
|
|
2832
2832
|
|
|
2833
2833
|
Examples:
|
|
2834
2834
|
|
|
2835
2835
|
skcapstone test
|
|
2836
2836
|
|
|
2837
|
-
skcapstone test --package
|
|
2837
|
+
skcapstone test --package skcomms
|
|
2838
2838
|
|
|
2839
2839
|
skcapstone test --fast --verbose
|
|
2840
2840
|
|
|
@@ -3037,7 +3037,7 @@ def peer_list(sk_home: str, json_out: bool):
|
|
|
3037
3037
|
def peer_remove(name: str, sk_home: str):
|
|
3038
3038
|
"""Remove a peer by name.
|
|
3039
3039
|
|
|
3040
|
-
Removes from both skcapstone and
|
|
3040
|
+
Removes from both skcapstone and skcomms registries.
|
|
3041
3041
|
|
|
3042
3042
|
Examples:
|
|
3043
3043
|
|
|
@@ -3212,8 +3212,8 @@ def dashboard(home: str, port: int, no_open: bool):
|
|
|
3212
3212
|
import webbrowser
|
|
3213
3213
|
try:
|
|
3214
3214
|
webbrowser.open(url)
|
|
3215
|
-
except Exception:
|
|
3216
|
-
|
|
3215
|
+
except Exception as exc:
|
|
3216
|
+
logger.warning("Failed to open browser for dashboard: %s", exc)
|
|
3217
3217
|
|
|
3218
3218
|
server = start_dashboard(home_path, port=port)
|
|
3219
3219
|
try:
|
|
@@ -3288,6 +3288,7 @@ def backup_create(home: str, output: str, no_encrypt: bool):
|
|
|
3288
3288
|
console.print(f" [green]Backup saved:[/] {result_path}")
|
|
3289
3289
|
|
|
3290
3290
|
except Exception as exc:
|
|
3291
|
+
logger.warning("_cli_monolith.py: %s", exc)
|
|
3291
3292
|
console.print(f" [red]Backup failed:[/] {exc}")
|
|
3292
3293
|
sys.exit(1)
|
|
3293
3294
|
|
|
@@ -3325,6 +3326,7 @@ def backup_restore(backup_file: str, home: str, force: bool):
|
|
|
3325
3326
|
console.print(f" [yellow]{exc}[/]")
|
|
3326
3327
|
sys.exit(1)
|
|
3327
3328
|
except Exception as exc:
|
|
3329
|
+
logger.warning("_cli_monolith.py: %s", exc)
|
|
3328
3330
|
console.print(f" [red]Restore failed:[/] {exc}")
|
|
3329
3331
|
sys.exit(1)
|
|
3330
3332
|
|
|
@@ -3396,7 +3398,7 @@ def chat():
|
|
|
3396
3398
|
def chat_send(peer: str, message: str, home: str, thread: Optional[str]):
|
|
3397
3399
|
"""Send a message to a peer agent.
|
|
3398
3400
|
|
|
3399
|
-
Stores locally and delivers via
|
|
3401
|
+
Stores locally and delivers via SKComms if transports
|
|
3400
3402
|
are configured.
|
|
3401
3403
|
|
|
3402
3404
|
Examples:
|
|
@@ -3434,7 +3436,7 @@ def chat_inbox(home: str, limit: int, poll: bool):
|
|
|
3434
3436
|
"""Show recent messages.
|
|
3435
3437
|
|
|
3436
3438
|
Displays messages from local history. Use --poll to check
|
|
3437
|
-
|
|
3439
|
+
SKComms transports for new messages first.
|
|
3438
3440
|
|
|
3439
3441
|
Examples:
|
|
3440
3442
|
|
|
@@ -5059,7 +5061,8 @@ def agents_messages(deployment_id: str, agent_name: Optional[str], limit: int, h
|
|
|
5059
5061
|
content[:80] + ("…" if len(content) > 80 else ""),
|
|
5060
5062
|
)
|
|
5061
5063
|
total_shown += 1
|
|
5062
|
-
except Exception:
|
|
5064
|
+
except Exception as e:
|
|
5065
|
+
logger.warning("_cli_monolith.py: %s", e)
|
|
5063
5066
|
continue
|
|
5064
5067
|
|
|
5065
5068
|
if table.row_count:
|
|
@@ -5106,7 +5109,8 @@ def agents_messages(deployment_id: str, agent_name: Optional[str], limit: int, h
|
|
|
5106
5109
|
content[:90] + ("…" if len(content) > 90 else ""),
|
|
5107
5110
|
)
|
|
5108
5111
|
total_shown += 1
|
|
5109
|
-
except Exception:
|
|
5112
|
+
except Exception as e:
|
|
5113
|
+
logger.warning("_cli_monolith.py: %s", e)
|
|
5110
5114
|
continue
|
|
5111
5115
|
|
|
5112
5116
|
if bc_table.row_count:
|
|
@@ -5353,8 +5357,8 @@ def agents_spawn(
|
|
|
5353
5357
|
prov_backend = DockerProvider()
|
|
5354
5358
|
elif prov_type == ProviderType.PROXMOX:
|
|
5355
5359
|
prov_backend = ProxmoxProvider()
|
|
5356
|
-
except Exception:
|
|
5357
|
-
|
|
5360
|
+
except Exception as exc:
|
|
5361
|
+
logger.warning("Failed to initialize provider backend for %s: %s", provider, exc)
|
|
5358
5362
|
|
|
5359
5363
|
# Auto-classify for display
|
|
5360
5364
|
detected_role, detected_model = classify_task(task)
|
|
@@ -5814,7 +5818,8 @@ def skills_install(source: str, agent: str | None, home: str, force: bool) -> No
|
|
|
5814
5818
|
try:
|
|
5815
5819
|
data = _json.loads(identity_path.read_text(encoding="utf-8"))
|
|
5816
5820
|
agent = data.get("name", "global")
|
|
5817
|
-
except Exception:
|
|
5821
|
+
except Exception as e:
|
|
5822
|
+
logger.warning("_cli_monolith.py: %s", e)
|
|
5818
5823
|
agent = "global"
|
|
5819
5824
|
else:
|
|
5820
5825
|
agent = "global"
|
|
@@ -33,6 +33,9 @@ from collections import deque
|
|
|
33
33
|
from datetime import datetime, timezone
|
|
34
34
|
from typing import Any
|
|
35
35
|
|
|
36
|
+
import logging
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
36
39
|
_MAXLEN = 100
|
|
37
40
|
|
|
38
41
|
_history: deque[dict] = deque(maxlen=_MAXLEN)
|
|
@@ -93,7 +96,8 @@ def _fan_out(event: dict) -> None:
|
|
|
93
96
|
for q in clients:
|
|
94
97
|
try:
|
|
95
98
|
q.put_nowait(chunk)
|
|
96
|
-
except Exception:
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.warning("activity.py: %s", e)
|
|
97
101
|
dead.add(q)
|
|
98
102
|
if dead:
|
|
99
103
|
with _clients_lock:
|
|
@@ -6,7 +6,7 @@ with you: identity, public key, contact transports, capabilities,
|
|
|
6
6
|
and trust level.
|
|
7
7
|
|
|
8
8
|
Cards are JSON files signed with the agent's PGP key. They can be
|
|
9
|
-
shared over
|
|
9
|
+
shared over SKComms, published to Nostr, posted as QR codes, or
|
|
10
10
|
exchanged via any out-of-band channel.
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
@@ -251,7 +251,8 @@ class AgentCard(BaseModel):
|
|
|
251
251
|
|
|
252
252
|
verification = pub_key.verify(pgp_message)
|
|
253
253
|
return bool(verification)
|
|
254
|
-
except Exception:
|
|
254
|
+
except Exception as e:
|
|
255
|
+
logger.warning("agent_card.py: %s", e)
|
|
255
256
|
return False
|
|
256
257
|
|
|
257
258
|
def save(self, filepath: str | Path) -> Path:
|