@smilintux/skcapstone 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +1 -1
- package/CLAUDE.md +17 -0
- package/docs/CUSTOM_AGENT.md +40 -28
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/openclaw-plugin/src/index.ts +2 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/archive-sessions.sh +7 -0
- package/scripts/install.sh +126 -1
- package/scripts/model-fallback-monitor.sh +4 -2
- package/scripts/refresh-anthropic-token.sh +9 -3
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/sk-agent-picker.sh +237 -0
- package/scripts/telegram-catchup-all.sh +2 -1
- package/scripts/watch-anthropic-token.sh +12 -17
- package/src/skcapstone/__init__.py +34 -2
- package/src/skcapstone/cli/__init__.py +3 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/context_cmd.py +16 -4
- package/src/skcapstone/cli/daemon.py +2 -1
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +4 -2
- package/src/skcapstone/cli/register_cmd.py +19 -3
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -30
- package/src/skcapstone/cli/soul.py +3 -3
- package/src/skcapstone/context_loader.py +9 -0
- package/src/skcapstone/coordination.py +9 -2
- package/src/skcapstone/daemon.py +22 -12
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -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/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +5 -5
- package/src/skcapstone/doctor.py +4 -2
- package/src/skcapstone/dreaming.py +3 -1
- package/src/skcapstone/fuse_mount.py +3 -1
- package/src/skcapstone/housekeeping.py +3 -3
- package/src/skcapstone/install_wizard.py +131 -0
- package/src/skcapstone/mcp_launcher.py +14 -1
- package/src/skcapstone/mcp_server.py +6 -21
- package/src/skcapstone/mcp_tools/notification_tools.py +3 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/migrate_multi_agent.py +7 -6
- package/src/skcapstone/notifications.py +6 -2
- package/src/skcapstone/onboard.py +19 -8
- package/src/skcapstone/operator_link.py +164 -0
- package/src/skcapstone/pillars/consciousness.py +2 -1
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/runtime.py +13 -3
- package/src/skcapstone/service_health.py +23 -10
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +11 -2
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomm-heartbeat.service +5 -2
- package/tests/conftest.py +21 -0
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_mcp_server.py +78 -33
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_operator_link.py +78 -0
- package/tests/test_runtime.py +21 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_trust_graph.py +18 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sk-agent-picker.sh — Sovereign agent picker for AI coding tools
|
|
3
|
+
#
|
|
4
|
+
# Source this file in ~/.bashrc or ~/.zshrc. It wraps `claude`, `codex`
|
|
5
|
+
# (OpenAI Codex CLI), and `opencode` with an agent-aware launcher that
|
|
6
|
+
# shows a numbered menu when multiple SK agents are configured.
|
|
7
|
+
#
|
|
8
|
+
# Also provides `skswitch` — a fast way to change the active agent for
|
|
9
|
+
# the current shell session (updates SKAGENT + legacy vars in one shot).
|
|
10
|
+
#
|
|
11
|
+
# Behaviour:
|
|
12
|
+
# - Zero agents found → launch tool normally (no SK home yet)
|
|
13
|
+
# - Exactly one agent → use it silently, no prompt
|
|
14
|
+
# - Multiple agents → numbered menu, default highlighted with →
|
|
15
|
+
# - SKAGENT is set → honour it, skip menu entirely
|
|
16
|
+
# - Pass --agent <name> → skip menu, use that agent directly
|
|
17
|
+
# - Any other args → forwarded to the underlying tool unchanged
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# claude # picker if multiple agents
|
|
21
|
+
# claude --agent lumina # direct launch
|
|
22
|
+
# SKAGENT=opus claude # env override
|
|
23
|
+
# skswitch lumina # change active agent for this shell
|
|
24
|
+
# skswitch # interactive picker
|
|
25
|
+
# codex # same picker logic
|
|
26
|
+
# opencode # same picker logic
|
|
27
|
+
#
|
|
28
|
+
# Source in shell config:
|
|
29
|
+
# source ~/.skenv/share/skcapstone/sk-agent-picker.sh
|
|
30
|
+
# Dev install:
|
|
31
|
+
# source ~/clawd/skcapstone-repos/skcapstone/scripts/sk-agent-picker.sh
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Core picker — returns chosen agent name on stdout, menu on stderr
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
_sk_pick_agent() {
|
|
37
|
+
local agents_dir="${SKCAPSTONE_HOME:-$HOME/.skcapstone}/agents"
|
|
38
|
+
local -a agents=()
|
|
39
|
+
|
|
40
|
+
if [[ -d "$agents_dir" ]]; then
|
|
41
|
+
while IFS= read -r entry; do
|
|
42
|
+
local name
|
|
43
|
+
name=$(basename "$entry")
|
|
44
|
+
# Skip template dirs, dotfiles, and non-directory entries
|
|
45
|
+
if [[ -d "$entry" && "$name" != *-template && "$name" != .* && "$name" != *.* ]]; then
|
|
46
|
+
agents+=("$name")
|
|
47
|
+
fi
|
|
48
|
+
done < <(find "$agents_dir" -mindepth 1 -maxdepth 1 -type d | sort)
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
local count="${#agents[@]}"
|
|
52
|
+
|
|
53
|
+
if [[ $count -eq 0 ]]; then
|
|
54
|
+
echo ""; return 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if [[ $count -eq 1 ]]; then
|
|
58
|
+
echo "${agents[0]}"; return 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Validate SKAGENT against actual agent list.
|
|
62
|
+
# If it's set but not in the list (stale env), fall back to first agent.
|
|
63
|
+
local env_agent="${SKAGENT:-${SKCAPSTONE_AGENT:-}}"
|
|
64
|
+
local default="${agents[0]}"
|
|
65
|
+
for agent in "${agents[@]}"; do
|
|
66
|
+
if [[ "$agent" == "$env_agent" ]]; then
|
|
67
|
+
default="$agent"
|
|
68
|
+
break
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
|
|
72
|
+
# Multi-agent menu
|
|
73
|
+
echo "" >&2
|
|
74
|
+
echo " ╔══════════════════════════════════╗" >&2
|
|
75
|
+
echo " ║ SKCapstone — Choose an Agent ║" >&2
|
|
76
|
+
echo " ╚══════════════════════════════════╝" >&2
|
|
77
|
+
echo "" >&2
|
|
78
|
+
|
|
79
|
+
local i=1
|
|
80
|
+
for agent in "${agents[@]}"; do
|
|
81
|
+
local marker=" "
|
|
82
|
+
if [[ "$agent" == "$default" ]]; then
|
|
83
|
+
marker="→ "
|
|
84
|
+
fi
|
|
85
|
+
printf " %s%2d) %s\n" "$marker" "$i" "$agent" >&2
|
|
86
|
+
(( i++ ))
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
echo "" >&2
|
|
90
|
+
printf " Agent [1-%d, Enter = %s]: " "$count" "$default" >&2
|
|
91
|
+
|
|
92
|
+
local choice
|
|
93
|
+
read -r choice </dev/tty
|
|
94
|
+
|
|
95
|
+
# Empty → use default
|
|
96
|
+
if [[ -z "$choice" ]]; then
|
|
97
|
+
echo "$default"; return 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Numeric
|
|
101
|
+
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= count )); then
|
|
102
|
+
echo "${agents[$((choice - 1))]}"; return 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# Name typed directly
|
|
106
|
+
for agent in "${agents[@]}"; do
|
|
107
|
+
if [[ "$agent" == "$choice" ]]; then
|
|
108
|
+
echo "$agent"; return 0
|
|
109
|
+
fi
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
# Invalid — use list-validated default (not stale env), re-show options
|
|
113
|
+
printf "\n ⚠ Unknown agent '%s'. Valid agents:\n" "$choice" >&2
|
|
114
|
+
for agent in "${agents[@]}"; do
|
|
115
|
+
printf " %s\n" "$agent" >&2
|
|
116
|
+
done
|
|
117
|
+
printf " Using default: %s\n\n" "$default" >&2
|
|
118
|
+
echo "$default"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Generic launcher used by all wrappers
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
_sk_launch() {
|
|
125
|
+
local tool="$1"; shift # the underlying binary (claude / codex / opencode)
|
|
126
|
+
local extra_flags="$1"; shift # tool-specific flags always appended (pass "" if none)
|
|
127
|
+
# remaining args collected below after parsing --agent
|
|
128
|
+
|
|
129
|
+
# Parse --agent <name> / --agent=<name> out of args first.
|
|
130
|
+
# SK_NO_PICKER=1 skips the menu entirely (for scripted/CI use).
|
|
131
|
+
local agent=""
|
|
132
|
+
local -a passthrough=()
|
|
133
|
+
local skip_next=0
|
|
134
|
+
|
|
135
|
+
for arg in "$@"; do
|
|
136
|
+
if [[ $skip_next -eq 1 ]]; then
|
|
137
|
+
agent="$arg"; skip_next=0; continue
|
|
138
|
+
fi
|
|
139
|
+
case "$arg" in
|
|
140
|
+
--agent) skip_next=1 ;;
|
|
141
|
+
--agent=*) agent="${arg#--agent=}" ;;
|
|
142
|
+
*) passthrough+=("$arg") ;;
|
|
143
|
+
esac
|
|
144
|
+
done
|
|
145
|
+
|
|
146
|
+
# --agent flag given → skip picker
|
|
147
|
+
# SK_NO_PICKER=1 → skip picker (scripted/CI use)
|
|
148
|
+
if [[ -z "$agent" && "${SK_NO_PICKER:-0}" != "1" ]]; then
|
|
149
|
+
agent=$(_sk_pick_agent)
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# Fallback: if picker returned empty (0 agents), just use SKAGENT
|
|
153
|
+
# or launch bare if that's also unset.
|
|
154
|
+
if [[ -z "$agent" ]]; then
|
|
155
|
+
agent="${SKAGENT:-${SKCAPSTONE_AGENT:-}}"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
if [[ -n "$agent" ]]; then
|
|
159
|
+
printf " ▶ Starting %s as agent: %s\n\n" "$tool" "$agent" >&2
|
|
160
|
+
if [[ -n "$extra_flags" ]]; then
|
|
161
|
+
SKAGENT="$agent" SKCAPSTONE_AGENT="$agent" SKMEMORY_AGENT="$agent" command "$tool" $extra_flags "${passthrough[@]}"
|
|
162
|
+
else
|
|
163
|
+
SKAGENT="$agent" SKCAPSTONE_AGENT="$agent" SKMEMORY_AGENT="$agent" command "$tool" "${passthrough[@]}"
|
|
164
|
+
fi
|
|
165
|
+
else
|
|
166
|
+
if [[ -n "$extra_flags" ]]; then
|
|
167
|
+
command "$tool" $extra_flags "${passthrough[@]}"
|
|
168
|
+
else
|
|
169
|
+
command "$tool" "${passthrough[@]}"
|
|
170
|
+
fi
|
|
171
|
+
fi
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# skswitch — change the active agent for the current shell session
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
function skswitch {
|
|
178
|
+
local agent="$1"
|
|
179
|
+
|
|
180
|
+
if [[ -z "$agent" ]]; then
|
|
181
|
+
# No argument — show interactive picker
|
|
182
|
+
agent=$(_sk_pick_agent)
|
|
183
|
+
if [[ -z "$agent" ]]; then
|
|
184
|
+
echo "No agents found in ${SKCAPSTONE_HOME:-$HOME/.skcapstone}/agents/" >&2
|
|
185
|
+
return 1
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Validate agent directory exists
|
|
190
|
+
local agent_dir="${SKCAPSTONE_HOME:-$HOME/.skcapstone}/agents/$agent"
|
|
191
|
+
if [[ ! -d "$agent_dir" ]]; then
|
|
192
|
+
echo "Agent not found: $agent" >&2
|
|
193
|
+
echo "Available agents:" >&2
|
|
194
|
+
local agents_dir="${SKCAPSTONE_HOME:-$HOME/.skcapstone}/agents"
|
|
195
|
+
if [[ -d "$agents_dir" ]]; then
|
|
196
|
+
find "$agents_dir" -mindepth 1 -maxdepth 1 -type d ! -name '*-template' ! -name '.*' -printf ' %f\n' | sort >&2
|
|
197
|
+
fi
|
|
198
|
+
return 1
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
export SKAGENT="$agent"
|
|
202
|
+
export SKCAPSTONE_AGENT="$agent"
|
|
203
|
+
export SKMEMORY_AGENT="$agent"
|
|
204
|
+
echo "Switched to agent: $agent"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# Per-tool wrapper functions
|
|
209
|
+
# Must unalias first — an active alias with the same name causes bash to
|
|
210
|
+
# expand it during function-definition parsing, producing a syntax error.
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
unalias claude 2>/dev/null || true
|
|
213
|
+
unalias codex 2>/dev/null || true
|
|
214
|
+
unalias opencode 2>/dev/null || true
|
|
215
|
+
|
|
216
|
+
# claude (Claude Code CLI)
|
|
217
|
+
function claude {
|
|
218
|
+
_sk_launch claude "--dangerously-skip-permissions" "$@"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# codex (OpenAI Codex CLI — https://github.com/openai/codex)
|
|
222
|
+
function codex {
|
|
223
|
+
_sk_launch codex "--full-auto" "$@"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# opencode (opencode.ai)
|
|
227
|
+
function opencode {
|
|
228
|
+
_sk_launch opencode "" "$@"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Export so sub-shells (tmux panes, etc.) inherit the functions
|
|
232
|
+
export -f _sk_pick_agent 2>/dev/null || true
|
|
233
|
+
export -f _sk_launch 2>/dev/null || true
|
|
234
|
+
export -f skswitch 2>/dev/null || true
|
|
235
|
+
export -f claude 2>/dev/null || true
|
|
236
|
+
export -f codex 2>/dev/null || true
|
|
237
|
+
export -f opencode 2>/dev/null || true
|
|
@@ -22,7 +22,8 @@ set -uo pipefail # no -e: individual group failures shouldn't stop the batch
|
|
|
22
22
|
SKENV="${HOME}/.skenv/bin"
|
|
23
23
|
SKCAPSTONE="${SKENV}/skcapstone"
|
|
24
24
|
CONFIG="${HOME}/.skcapstone/agents/lumina/config/telegram.yaml"
|
|
25
|
-
export
|
|
25
|
+
export SKAGENT="${SKAGENT:-lumina}"
|
|
26
|
+
export SKCAPSTONE_AGENT="${SKAGENT}"
|
|
26
27
|
export PATH="${SKENV}:${PATH}"
|
|
27
28
|
|
|
28
29
|
# Parse args
|
|
@@ -36,9 +36,10 @@ sync_token() {
|
|
|
36
36
|
local expires_in
|
|
37
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
38
|
|
|
39
|
-
# Read current token from
|
|
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"
|
|
40
41
|
local current_token
|
|
41
|
-
current_token=$(
|
|
42
|
+
current_token=$(cat "$state_file" 2>/dev/null || echo "")
|
|
42
43
|
|
|
43
44
|
if [ "$new_token" = "$current_token" ]; then
|
|
44
45
|
log "Token unchanged (expires in $expires_in)"
|
|
@@ -47,20 +48,14 @@ sync_token() {
|
|
|
47
48
|
|
|
48
49
|
log "Token changed! Syncing... (new token expires in $expires_in)"
|
|
49
50
|
|
|
50
|
-
# 1.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
json.dump(cfg, f, indent=2)
|
|
59
|
-
f.write('\n')
|
|
60
|
-
PYEOF
|
|
61
|
-
log "Updated openclaw.json"
|
|
62
|
-
|
|
63
|
-
# 2. Update .env
|
|
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)
|
|
64
59
|
if grep -q "^ANTHROPIC_API_KEY=" "$OPENCLAW_ENV" 2>/dev/null; then
|
|
65
60
|
sed -i "s|^ANTHROPIC_API_KEY=.*|ANTHROPIC_API_KEY=$new_token|" "$OPENCLAW_ENV"
|
|
66
61
|
else
|
|
@@ -68,7 +63,7 @@ PYEOF
|
|
|
68
63
|
fi
|
|
69
64
|
log "Updated .env"
|
|
70
65
|
|
|
71
|
-
# 3. Update systemd override
|
|
66
|
+
# 3. Update systemd override (ANTHROPIC_API_KEY kept for claude-code-api server)
|
|
72
67
|
if [ -f "$OVERRIDE_CONF" ]; then
|
|
73
68
|
local nvidia_key
|
|
74
69
|
nvidia_key=$(grep "NVIDIA_API_KEY=" "$OVERRIDE_CONF" 2>/dev/null | sed 's/.*NVIDIA_API_KEY=//' || true)
|
|
@@ -25,11 +25,38 @@ def _default_home() -> str:
|
|
|
25
25
|
return os.path.expanduser("~/.skcapstone")
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _detect_active_agent(root: str | None = None) -> str | None:
|
|
29
|
+
"""Best-effort active agent discovery.
|
|
30
|
+
|
|
31
|
+
Resolution order:
|
|
32
|
+
1. Explicit SKCAPSTONE_AGENT environment variable
|
|
33
|
+
2. First non-template directory under ~/.skcapstone/agents
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The active agent name if one can be resolved, else None.
|
|
37
|
+
"""
|
|
38
|
+
env_agent = (os.environ.get("SKAGENT") or os.environ.get("SKCAPSTONE_AGENT", "")).strip()
|
|
39
|
+
if env_agent:
|
|
40
|
+
return env_agent
|
|
41
|
+
|
|
42
|
+
base = Path(root or os.environ.get("SKCAPSTONE_HOME", _default_home())).expanduser()
|
|
43
|
+
agents_dir = base / "agents"
|
|
44
|
+
if not agents_dir.exists():
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
candidates = sorted(
|
|
48
|
+
entry.name
|
|
49
|
+
for entry in agents_dir.iterdir()
|
|
50
|
+
if entry.is_dir() and not entry.name.endswith("-template")
|
|
51
|
+
)
|
|
52
|
+
return candidates[0] if candidates else None
|
|
53
|
+
|
|
54
|
+
|
|
28
55
|
# Root of the skcapstone tree (shared infra lives here)
|
|
29
56
|
AGENT_HOME = os.environ.get("SKCAPSTONE_HOME", _default_home())
|
|
30
57
|
|
|
31
58
|
# Which agent this process is running as (set by daemon/connector)
|
|
32
|
-
SKCAPSTONE_AGENT =
|
|
59
|
+
SKCAPSTONE_AGENT = _detect_active_agent() or ""
|
|
33
60
|
|
|
34
61
|
# Default daemon port
|
|
35
62
|
DEFAULT_PORT = int(os.environ.get("SKCAPSTONE_PORT", "9383"))
|
|
@@ -59,13 +86,18 @@ def agent_home(agent_name: str | None = None) -> Path:
|
|
|
59
86
|
Returns:
|
|
60
87
|
Path to the agent-specific home directory.
|
|
61
88
|
"""
|
|
62
|
-
name = agent_name or SKCAPSTONE_AGENT
|
|
89
|
+
name = agent_name or SKCAPSTONE_AGENT or _detect_active_agent()
|
|
63
90
|
root = Path(AGENT_HOME).expanduser()
|
|
64
91
|
if name:
|
|
65
92
|
return root / "agents" / name
|
|
66
93
|
return root
|
|
67
94
|
|
|
68
95
|
|
|
96
|
+
def active_agent_name() -> str | None:
|
|
97
|
+
"""Return the currently active agent name, if one can be resolved."""
|
|
98
|
+
return SKCAPSTONE_AGENT or _detect_active_agent()
|
|
99
|
+
|
|
100
|
+
|
|
69
101
|
def shared_home() -> Path:
|
|
70
102
|
"""Return the shared root directory (~/.skcapstone/).
|
|
71
103
|
|
|
@@ -19,7 +19,7 @@ from .. import __version__
|
|
|
19
19
|
@click.group()
|
|
20
20
|
@click.version_option(version=__version__, prog_name="skcapstone")
|
|
21
21
|
@click.option(
|
|
22
|
-
"--agent", envvar="
|
|
22
|
+
"--agent", envvar="SKAGENT", default="",
|
|
23
23
|
help="Agent name — resolves home to {root}/agents/{name}/",
|
|
24
24
|
)
|
|
25
25
|
@click.pass_context
|
|
@@ -91,6 +91,7 @@ from .skseed import register_skseed_commands
|
|
|
91
91
|
from .service_cmd import register_service_commands
|
|
92
92
|
from .telegram import register_telegram_commands
|
|
93
93
|
from .joule_cmd import register_joule_commands
|
|
94
|
+
from .alerts import register_alerts_commands
|
|
94
95
|
|
|
95
96
|
register_setup_commands(main)
|
|
96
97
|
register_shell_commands(main)
|
|
@@ -144,3 +145,4 @@ register_skseed_commands(main)
|
|
|
144
145
|
register_service_commands(main)
|
|
145
146
|
register_telegram_commands(main)
|
|
146
147
|
register_joule_commands(main)
|
|
148
|
+
register_alerts_commands(main)
|
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
|
|
9
|
-
from ._common import AGENT_HOME, console
|
|
9
|
+
from ._common import AGENT_HOME, SKCAPSTONE_AGENT, console, resolve_agent_home
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def register_context_commands(main: click.Group) -> None:
|
|
@@ -22,7 +22,11 @@ def register_context_commands(main: click.Group) -> None:
|
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
@context.command("show")
|
|
25
|
-
@click.option(
|
|
25
|
+
@click.option(
|
|
26
|
+
"--home",
|
|
27
|
+
default=str(resolve_agent_home(SKCAPSTONE_AGENT)),
|
|
28
|
+
type=click.Path(),
|
|
29
|
+
)
|
|
26
30
|
@click.option(
|
|
27
31
|
"--format",
|
|
28
32
|
"fmt",
|
|
@@ -55,7 +59,11 @@ def register_context_commands(main: click.Group) -> None:
|
|
|
55
59
|
click.echo(FORMATTERS[fmt](ctx))
|
|
56
60
|
|
|
57
61
|
@context.command("generate")
|
|
58
|
-
@click.option(
|
|
62
|
+
@click.option(
|
|
63
|
+
"--home",
|
|
64
|
+
default=str(resolve_agent_home(SKCAPSTONE_AGENT)),
|
|
65
|
+
type=click.Path(),
|
|
66
|
+
)
|
|
59
67
|
@click.option("--memories", "-n", default=10, help="Max recent memories to include.")
|
|
60
68
|
@click.option(
|
|
61
69
|
"--target",
|
|
@@ -95,7 +103,11 @@ def register_context_commands(main: click.Group) -> None:
|
|
|
95
103
|
console.print()
|
|
96
104
|
|
|
97
105
|
@main.command("refresh-context")
|
|
98
|
-
@click.option(
|
|
106
|
+
@click.option(
|
|
107
|
+
"--home",
|
|
108
|
+
default=str(resolve_agent_home(SKCAPSTONE_AGENT)),
|
|
109
|
+
type=click.Path(),
|
|
110
|
+
)
|
|
99
111
|
@click.option("--memories", "-n", default=10, help="Max recent memories to embed.")
|
|
100
112
|
@click.option(
|
|
101
113
|
"--dest",
|
|
@@ -140,7 +140,8 @@ def register_daemon_commands(main: click.Group) -> None:
|
|
|
140
140
|
effective_port = _resolve_agent_port(agent, port)
|
|
141
141
|
|
|
142
142
|
if agent:
|
|
143
|
-
# Propagate identity to child imports that read
|
|
143
|
+
# Propagate identity to child imports that read SKAGENT.
|
|
144
|
+
os.environ["SKAGENT"] = agent
|
|
144
145
|
os.environ["SKCAPSTONE_AGENT"] = agent
|
|
145
146
|
|
|
146
147
|
if not home_path.exists():
|
|
@@ -439,7 +439,7 @@ def register_joule_commands(main: click.Group) -> None:
|
|
|
439
439
|
@joule_group.command("dashboard")
|
|
440
440
|
@click.option(
|
|
441
441
|
"--agent", "-a", "agent_name", default=None,
|
|
442
|
-
help="Agent name (default:
|
|
442
|
+
help="Agent name (default: current agent).",
|
|
443
443
|
)
|
|
444
444
|
def dashboard_cmd(agent_name: str | None):
|
|
445
445
|
"""Show a financial dashboard for an agent."""
|
|
@@ -451,7 +451,9 @@ def register_joule_commands(main: click.Group) -> None:
|
|
|
451
451
|
|
|
452
452
|
from ..skjoule import JouleEngine, TransactionKind
|
|
453
453
|
|
|
454
|
-
|
|
454
|
+
from .. import active_agent_name
|
|
455
|
+
|
|
456
|
+
agent_name = agent_name or active_agent_name()
|
|
455
457
|
engine = JouleEngine(home=Path(SHARED_ROOT).expanduser())
|
|
456
458
|
wallet = engine.get_wallet(agent_name)
|
|
457
459
|
balance = wallet.balance
|
|
@@ -624,4 +626,6 @@ def _resolve_agent(agent_name: str | None) -> str:
|
|
|
624
626
|
if agent_name:
|
|
625
627
|
return agent_name
|
|
626
628
|
from .. import SKCAPSTONE_AGENT
|
|
627
|
-
|
|
629
|
+
from .. import active_agent_name
|
|
630
|
+
|
|
631
|
+
return SKCAPSTONE_AGENT or active_agent_name() or ""
|
|
@@ -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
|
|
|
@@ -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:
|
|
@@ -107,6 +107,7 @@ def register_register_commands(main: click.Group) -> None:
|
|
|
107
107
|
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
108
108
|
table.add_column("Package", style="cyan")
|
|
109
109
|
table.add_column("Skill", style="dim")
|
|
110
|
+
table.add_column("Codex")
|
|
110
111
|
table.add_column("MCP")
|
|
111
112
|
table.add_column("OpenClaw Plugin")
|
|
112
113
|
|
|
@@ -143,6 +144,21 @@ def register_register_commands(main: click.Group) -> None:
|
|
|
143
144
|
else:
|
|
144
145
|
mcp_str = str(mcp_info)
|
|
145
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
|
+
|
|
146
162
|
plugin_action = pkg_result.get("openclaw_plugin", "")
|
|
147
163
|
if plugin_action == "created":
|
|
148
164
|
plugin_str = "[green]created[/]"
|
|
@@ -157,7 +173,7 @@ def register_register_commands(main: click.Group) -> None:
|
|
|
157
173
|
else:
|
|
158
174
|
plugin_str = f"[dim]{plugin_action}[/]"
|
|
159
175
|
|
|
160
|
-
table.add_row(name, skill_str, mcp_str, plugin_str)
|
|
176
|
+
table.add_row(name, skill_str, codex_str, mcp_str, plugin_str)
|
|
161
177
|
|
|
162
178
|
console.print(table)
|
|
163
179
|
console.print()
|
|
@@ -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))
|