@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.
Files changed (71) hide show
  1. package/.github/workflows/publish.yml +1 -1
  2. package/CLAUDE.md +17 -0
  3. package/docs/CUSTOM_AGENT.md +40 -28
  4. package/docs/SOUL_SWAPPER.md +5 -5
  5. package/docs/hammertime-audit.md +402 -0
  6. package/openclaw-plugin/src/index.ts +2 -1
  7. package/package.json +1 -1
  8. package/pyproject.toml +1 -1
  9. package/scripts/archive-sessions.sh +7 -0
  10. package/scripts/install.sh +126 -1
  11. package/scripts/model-fallback-monitor.sh +4 -2
  12. package/scripts/refresh-anthropic-token.sh +9 -3
  13. package/scripts/release.sh +98 -0
  14. package/scripts/session-to-memory.py +219 -0
  15. package/scripts/sk-agent-picker.sh +237 -0
  16. package/scripts/telegram-catchup-all.sh +2 -1
  17. package/scripts/watch-anthropic-token.sh +12 -17
  18. package/src/skcapstone/__init__.py +34 -2
  19. package/src/skcapstone/cli/__init__.py +3 -1
  20. package/src/skcapstone/cli/_common.py +1 -0
  21. package/src/skcapstone/cli/context_cmd.py +16 -4
  22. package/src/skcapstone/cli/daemon.py +2 -1
  23. package/src/skcapstone/cli/joule_cmd.py +7 -3
  24. package/src/skcapstone/cli/memory.py +4 -2
  25. package/src/skcapstone/cli/register_cmd.py +19 -3
  26. package/src/skcapstone/cli/session.py +25 -0
  27. package/src/skcapstone/cli/setup.py +96 -30
  28. package/src/skcapstone/cli/soul.py +3 -3
  29. package/src/skcapstone/context_loader.py +9 -0
  30. package/src/skcapstone/coordination.py +9 -2
  31. package/src/skcapstone/daemon.py +22 -12
  32. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  33. package/src/skcapstone/defaults/claude/settings.json +74 -0
  34. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  35. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  36. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  37. package/src/skcapstone/defaults/unhinged.json +13 -0
  38. package/src/skcapstone/discovery.py +5 -5
  39. package/src/skcapstone/doctor.py +4 -2
  40. package/src/skcapstone/dreaming.py +3 -1
  41. package/src/skcapstone/fuse_mount.py +3 -1
  42. package/src/skcapstone/housekeeping.py +3 -3
  43. package/src/skcapstone/install_wizard.py +131 -0
  44. package/src/skcapstone/mcp_launcher.py +14 -1
  45. package/src/skcapstone/mcp_server.py +6 -21
  46. package/src/skcapstone/mcp_tools/notification_tools.py +3 -1
  47. package/src/skcapstone/memory_engine.py +10 -3
  48. package/src/skcapstone/migrate_multi_agent.py +7 -6
  49. package/src/skcapstone/notifications.py +6 -2
  50. package/src/skcapstone/onboard.py +19 -8
  51. package/src/skcapstone/operator_link.py +164 -0
  52. package/src/skcapstone/pillars/consciousness.py +2 -1
  53. package/src/skcapstone/pillars/identity.py +51 -7
  54. package/src/skcapstone/pillars/memory.py +9 -3
  55. package/src/skcapstone/runtime.py +13 -3
  56. package/src/skcapstone/service_health.py +23 -10
  57. package/src/skcapstone/session_briefing.py +108 -0
  58. package/src/skcapstone/trust_graph.py +40 -5
  59. package/src/skcapstone/unified_search.py +11 -2
  60. package/systemd/skcapstone.service +4 -6
  61. package/systemd/skcapstone@.service +7 -8
  62. package/systemd/skcomm-heartbeat.service +5 -2
  63. package/tests/conftest.py +21 -0
  64. package/tests/test_agent_home_scaffold.py +34 -0
  65. package/tests/test_backup.py +2 -1
  66. package/tests/test_mcp_server.py +78 -33
  67. package/tests/test_multi_agent.py +31 -29
  68. package/tests/test_operator_link.py +78 -0
  69. package/tests/test_runtime.py +21 -0
  70. package/tests/test_session_briefing.py +130 -0
  71. 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 SKCAPSTONE_AGENT="${SKCAPSTONE_AGENT:-lumina}"
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 OpenClaw
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=$(python3 -c "import json; print(json.load(open('$OPENCLAW_JSON'))['models']['providers']['anthropic']['apiKey'])" 2>/dev/null || echo "")
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. Update openclaw.json
51
- python3 << PYEOF
52
- import json
53
- with open('$OPENCLAW_JSON') as f:
54
- cfg = json.load(f)
55
- if 'anthropic' in cfg.get('models', {}).get('providers', {}):
56
- cfg['models']['providers']['anthropic']['apiKey'] = '$new_token'
57
- with open('$OPENCLAW_JSON', 'w') as f:
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 = os.environ.get("SKCAPSTONE_AGENT", "lumina")
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="SKCAPSTONE_AGENT", default="",
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)
@@ -58,6 +58,7 @@ def apply_agent_override(agent: str) -> None:
58
58
  """
59
59
  if agent:
60
60
  _pkg.SKCAPSTONE_AGENT = agent
61
+ os.environ["SKAGENT"] = agent
61
62
  os.environ["SKCAPSTONE_AGENT"] = agent
62
63
 
63
64
 
@@ -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("--home", default=AGENT_HOME, type=click.Path())
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("--home", default=AGENT_HOME, type=click.Path())
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("--home", default=AGENT_HOME, type=click.Path())
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 SKCAPSTONE_AGENT.
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: lumina).",
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
- agent_name = agent_name or "lumina"
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
- return SKCAPSTONE_AGENT or "lumina"
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: SKCAPSTONE_AGENT or 'lumina').")
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
- agent_name = agent or os.environ.get("SKCAPSTONE_AGENT", "lumina")
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))