claude-smart 0.2.30 → 0.2.32
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/README.md +2 -2
- package/bin/claude-smart.js +172 -18
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/dashboard/app/api/config/route.ts +11 -2
- package/plugin/dashboard/app/api/health/route.ts +45 -6
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +15 -7
- package/plugin/dashboard/app/api/rules/applied/route.ts +27 -0
- package/plugin/dashboard/app/configure/env/page.tsx +36 -31
- package/plugin/dashboard/app/configure/layout.tsx +1 -1
- package/plugin/dashboard/app/configure/server/page.tsx +8 -14
- package/plugin/dashboard/app/dashboard/page.tsx +311 -115
- package/plugin/dashboard/app/globals.css +80 -66
- package/plugin/dashboard/app/layout.tsx +13 -10
- package/plugin/dashboard/app/preferences/[id]/page.tsx +21 -32
- package/plugin/dashboard/app/preferences/page.tsx +154 -54
- package/plugin/dashboard/app/preferences/project/[id]/page.tsx +1 -0
- package/plugin/dashboard/app/rules/[id]/page.tsx +51 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +4 -4
- package/plugin/dashboard/app/sessions/page.tsx +14 -10
- package/plugin/dashboard/app/skills/page.tsx +175 -56
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +22 -38
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +20 -37
- package/plugin/dashboard/components/common/delete-learning-danger-zone.tsx +166 -0
- package/plugin/dashboard/components/common/empty-state.tsx +4 -2
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +5 -3
- package/plugin/dashboard/components/common/page-tabs.tsx +4 -4
- package/plugin/dashboard/components/common/stat-card.tsx +9 -5
- package/plugin/dashboard/components/layout/sidebar.tsx +24 -9
- package/plugin/dashboard/components/layout/top-bar.tsx +37 -25
- package/plugin/dashboard/components/ui/input.tsx +1 -0
- package/plugin/dashboard/hooks/use-settings.tsx +30 -61
- package/plugin/dashboard/hooks/use-stall-state.ts +5 -9
- package/plugin/dashboard/lib/config-file.ts +2 -0
- package/plugin/dashboard/lib/reflexio-client.ts +23 -48
- package/plugin/dashboard/lib/session-reader.ts +222 -6
- package/plugin/dashboard/lib/types.ts +20 -1
- package/plugin/dashboard/package-lock.json +70 -95
- package/plugin/dashboard/package.json +5 -2
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +126 -0
- package/plugin/scripts/backend-service.sh +32 -7
- package/plugin/scripts/cli.sh +4 -2
- package/plugin/scripts/codex-hook.js +100 -3
- package/plugin/scripts/dashboard-service.sh +98 -19
- package/plugin/scripts/hook_entry.sh +32 -11
- package/plugin/scripts/smart-install.sh +27 -44
- package/plugin/src/claude_smart/cli.py +204 -20
- package/plugin/src/claude_smart/context_format.py +244 -6
- package/plugin/src/claude_smart/context_inject.py +8 -1
- package/plugin/src/claude_smart/cs_cite.py +186 -34
- package/plugin/src/claude_smart/env_config.py +102 -0
- package/plugin/src/claude_smart/events/session_end.py +171 -6
- package/plugin/src/claude_smart/events/session_start.py +26 -2
- package/plugin/src/claude_smart/events/stop.py +48 -9
- package/plugin/src/claude_smart/hook.py +62 -4
- package/plugin/src/claude_smart/hook_log.py +301 -0
- package/plugin/src/claude_smart/internal_call.py +30 -0
- package/plugin/src/claude_smart/publish.py +5 -0
- package/plugin/src/claude_smart/reflexio_adapter.py +102 -26
- package/plugin/uv.lock +1 -1
|
@@ -14,6 +14,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
|
14
14
|
claude_smart_source_login_path
|
|
15
15
|
claude_smart_prepend_astral_bins
|
|
16
16
|
claude_smart_prepend_node_bins
|
|
17
|
+
claude_smart_source_reflexio_env
|
|
17
18
|
|
|
18
19
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
19
20
|
REPO_ROOT="$(cd "$HERE/../.." && pwd)"
|
|
@@ -47,7 +48,7 @@ acquire_install_lock() {
|
|
|
47
48
|
exec 9>"$INSTALL_LOCK"
|
|
48
49
|
if ! flock 9; then
|
|
49
50
|
echo "[claude-smart] install lock failed; continuing without serialization" >&2
|
|
50
|
-
|
|
51
|
+
claude_smart_emit_continue
|
|
51
52
|
exit 0
|
|
52
53
|
fi
|
|
53
54
|
return 0
|
|
@@ -80,45 +81,23 @@ acquire_install_lock
|
|
|
80
81
|
rm -f "$FAILURE_MARKER"
|
|
81
82
|
|
|
82
83
|
write_failure() {
|
|
83
|
-
local reason
|
|
84
|
+
local reason fp_hash
|
|
84
85
|
reason="$1"
|
|
85
|
-
|
|
86
|
+
fp_hash="$(claude_smart_install_fingerprint_hash "$PLUGIN_ROOT" "$HERE" 2>/dev/null || true)"
|
|
87
|
+
{
|
|
88
|
+
printf '%s\n' "$reason"
|
|
89
|
+
if [ -n "$fp_hash" ]; then
|
|
90
|
+
printf 'fingerprint=%s\n' "$fp_hash"
|
|
91
|
+
fi
|
|
92
|
+
} > "$FAILURE_MARKER"
|
|
86
93
|
rm -f "$SUCCESS_MARKER"
|
|
87
94
|
echo "[claude-smart] install failed: $reason" >&2
|
|
88
|
-
|
|
95
|
+
claude_smart_emit_continue
|
|
89
96
|
exit 0
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
fingerprint_file() {
|
|
93
|
-
local path
|
|
94
|
-
path="$1"
|
|
95
|
-
if [ -f "$path" ]; then
|
|
96
|
-
cksum "$path" 2>/dev/null | awk '{print $1 ":" $2}'
|
|
97
|
-
else
|
|
98
|
-
printf 'missing\n'
|
|
99
|
-
fi
|
|
100
|
-
}
|
|
101
|
-
|
|
102
99
|
install_fingerprint() {
|
|
103
|
-
|
|
104
|
-
printf 'smart_install=%s\n' "$(fingerprint_file "$HERE/smart-install.sh")"
|
|
105
|
-
printf 'pyproject=%s\n' "$(fingerprint_file "$PLUGIN_ROOT/pyproject.toml")"
|
|
106
|
-
printf 'uv_lock=%s\n' "$(fingerprint_file "$PLUGIN_ROOT/uv.lock")"
|
|
107
|
-
# Resolved python interpreter — catches a system upgrade (3.12.4 → 3.12.5)
|
|
108
|
-
# that would otherwise let install_complete return true against a venv
|
|
109
|
-
# built against a now-deleted interpreter.
|
|
110
|
-
if command -v uv >/dev/null 2>&1; then
|
|
111
|
-
printf 'python=%s\n' "$(uv python find 3.12 2>/dev/null || echo missing)"
|
|
112
|
-
else
|
|
113
|
-
printf 'python=no-uv\n'
|
|
114
|
-
fi
|
|
115
|
-
if [ -d "$PLUGIN_ROOT/dashboard" ]; then
|
|
116
|
-
printf 'dashboard_pkg=%s\n' "$(fingerprint_file "$PLUGIN_ROOT/dashboard/package.json")"
|
|
117
|
-
printf 'dashboard_lock=%s\n' "$(fingerprint_file "$PLUGIN_ROOT/dashboard/package-lock.json")"
|
|
118
|
-
else
|
|
119
|
-
printf 'dashboard_pkg=none\n'
|
|
120
|
-
printf 'dashboard_lock=none\n'
|
|
121
|
-
fi
|
|
100
|
+
claude_smart_install_fingerprint "$PLUGIN_ROOT" "$HERE"
|
|
122
101
|
}
|
|
123
102
|
|
|
124
103
|
install_complete() {
|
|
@@ -127,8 +106,10 @@ install_complete() {
|
|
|
127
106
|
command -v uv >/dev/null 2>&1 || return 1
|
|
128
107
|
[ -d "$PLUGIN_ROOT/.venv" ] || return 1
|
|
129
108
|
[ -f "$HOME/.reflexio/.env" ] || return 1
|
|
130
|
-
|
|
131
|
-
|
|
109
|
+
if [ -z "${REFLEXIO_API_KEY:-}" ]; then
|
|
110
|
+
grep -q '^CLAUDE_SMART_USE_LOCAL_CLI=' "$HOME/.reflexio/.env" || return 1
|
|
111
|
+
grep -q '^CLAUDE_SMART_USE_LOCAL_EMBEDDING=' "$HOME/.reflexio/.env" || return 1
|
|
112
|
+
fi
|
|
132
113
|
if [ -d "$PLUGIN_ROOT/dashboard" ]; then
|
|
133
114
|
[ -d "$PLUGIN_ROOT/dashboard/.next" ] || [ -f "$MARKER_DIR/dashboard-build.pid" ] || [ -f "$(claude_smart_dashboard_unavailable_marker)" ] || return 1
|
|
134
115
|
fi
|
|
@@ -382,7 +363,7 @@ fi
|
|
|
382
363
|
preflight_supported_runtime_platform
|
|
383
364
|
|
|
384
365
|
if install_complete; then
|
|
385
|
-
|
|
366
|
+
claude_smart_emit_continue
|
|
386
367
|
exit 0
|
|
387
368
|
fi
|
|
388
369
|
|
|
@@ -453,13 +434,15 @@ fi
|
|
|
453
434
|
REFLEXIO_ENV="$HOME/.reflexio/.env"
|
|
454
435
|
mkdir -p "$(dirname "$REFLEXIO_ENV")"
|
|
455
436
|
touch "$REFLEXIO_ENV"
|
|
456
|
-
if
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
437
|
+
if [ -z "${REFLEXIO_API_KEY:-}" ]; then
|
|
438
|
+
if ! grep -q '^CLAUDE_SMART_USE_LOCAL_CLI=' "$REFLEXIO_ENV"; then
|
|
439
|
+
printf '\n# Route reflexio generation through the local Claude Code CLI\nCLAUDE_SMART_USE_LOCAL_CLI=1\n' >> "$REFLEXIO_ENV"
|
|
440
|
+
echo "[claude-smart] appended CLAUDE_SMART_USE_LOCAL_CLI=1 to $REFLEXIO_ENV" >&2
|
|
441
|
+
fi
|
|
442
|
+
if ! grep -q '^CLAUDE_SMART_USE_LOCAL_EMBEDDING=' "$REFLEXIO_ENV"; then
|
|
443
|
+
printf '# Use the in-process ONNX embedder (chromadb) — no API key for semantic search\nCLAUDE_SMART_USE_LOCAL_EMBEDDING=1\n' >> "$REFLEXIO_ENV"
|
|
444
|
+
echo "[claude-smart] appended CLAUDE_SMART_USE_LOCAL_EMBEDDING=1 to $REFLEXIO_ENV" >&2
|
|
445
|
+
fi
|
|
463
446
|
fi
|
|
464
447
|
# Migrate stale REFLEXIO_URL from reflexio's library default (8081) to the
|
|
465
448
|
# plugin backend port (8071). Matches the quoted and unquoted forms but
|
|
@@ -533,4 +516,4 @@ fi
|
|
|
533
516
|
|
|
534
517
|
write_success_marker
|
|
535
518
|
echo "[claude-smart] install complete. Backend and dashboard auto-start on session start." >&2
|
|
536
|
-
|
|
519
|
+
claude_smart_emit_continue
|
|
@@ -33,10 +33,11 @@ import time
|
|
|
33
33
|
from dataclasses import dataclass
|
|
34
34
|
from pathlib import Path
|
|
35
35
|
|
|
36
|
-
from claude_smart import context_format, ids, publish, state
|
|
36
|
+
from claude_smart import context_format, env_config, ids, publish, state
|
|
37
37
|
from claude_smart.reflexio_adapter import Adapter
|
|
38
38
|
|
|
39
|
-
_REFLEXIO_ENV_PATH =
|
|
39
|
+
_REFLEXIO_ENV_PATH = env_config.REFLEXIO_ENV_PATH
|
|
40
|
+
_MANAGED_REFLEXIO_URL = env_config.MANAGED_REFLEXIO_URL
|
|
40
41
|
_DEFAULT_MARKETPLACE_SOURCE = "ReflexioAI/claude-smart"
|
|
41
42
|
_PLUGIN_SPEC = "claude-smart@reflexioai"
|
|
42
43
|
_CODEX_MARKETPLACE_NAME = "reflexioai"
|
|
@@ -70,6 +71,16 @@ _BACKEND_SCRIPT = _SCRIPTS_DIR / "backend-service.sh"
|
|
|
70
71
|
_DASHBOARD_SCRIPT = _SCRIPTS_DIR / "dashboard-service.sh"
|
|
71
72
|
_REFLEXIO_DIR = Path.home() / ".reflexio"
|
|
72
73
|
_STATE_DIR = Path.home() / ".claude-smart"
|
|
74
|
+
_LOCAL_DATA_NOTICE = (
|
|
75
|
+
"Local data was kept so reinstalling claude-smart can reuse your learned "
|
|
76
|
+
"rules, sessions, logs, and local Reflexio data.\n"
|
|
77
|
+
"Kept folders:\n"
|
|
78
|
+
" ~/.claude-smart\n"
|
|
79
|
+
" ~/.reflexio\n"
|
|
80
|
+
"Delete them only if you want a full reset or need to remove local "
|
|
81
|
+
"claude-smart data from this machine:\n"
|
|
82
|
+
" rm -rf ~/.claude-smart ~/.reflexio\n"
|
|
83
|
+
)
|
|
73
84
|
_INSTALL_FAILURE_MARKER = _STATE_DIR / "install-failed"
|
|
74
85
|
_DEFAULT_STORAGE_ROOT = _REFLEXIO_DIR / "data"
|
|
75
86
|
_REFLEXIO_CONFIG_PATH = _REFLEXIO_DIR / "configs" / "config_self-host-org.json"
|
|
@@ -127,9 +138,21 @@ def _seed_reflexio_env() -> list[str]:
|
|
|
127
138
|
prefix = "" if not existing or existing.endswith("\n") else "\n"
|
|
128
139
|
with _REFLEXIO_ENV_PATH.open("a") as fh:
|
|
129
140
|
fh.write(prefix + "\n".join(f"{f}=1" for f in missing) + "\n")
|
|
141
|
+
_REFLEXIO_ENV_PATH.chmod(0o600)
|
|
130
142
|
return missing
|
|
131
143
|
|
|
132
144
|
|
|
145
|
+
def _seed_managed_reflexio_env(*, api_key: str, reflexio_url: str) -> list[str]:
|
|
146
|
+
"""Write managed Reflexio connection settings to ``~/.reflexio/.env``."""
|
|
147
|
+
return env_config.set_env_vars(
|
|
148
|
+
_REFLEXIO_ENV_PATH,
|
|
149
|
+
{
|
|
150
|
+
env_config.REFLEXIO_URL_ENV: reflexio_url,
|
|
151
|
+
env_config.REFLEXIO_API_KEY_ENV: api_key,
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
133
156
|
def _find_claude_code_plugin_root() -> Path | None:
|
|
134
157
|
"""Locate the installed Claude Code plugin root after native install."""
|
|
135
158
|
cache_root = (
|
|
@@ -162,10 +185,9 @@ def _find_claude_code_plugin_root() -> Path | None:
|
|
|
162
185
|
]
|
|
163
186
|
)
|
|
164
187
|
for candidate in candidates:
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
):
|
|
188
|
+
if (candidate / "pyproject.toml").is_file() and (
|
|
189
|
+
candidate / "scripts" / "smart-install.sh"
|
|
190
|
+
).is_file():
|
|
169
191
|
return candidate
|
|
170
192
|
return None
|
|
171
193
|
|
|
@@ -205,7 +227,8 @@ def _bootstrap_claude_code_install() -> tuple[bool, str]:
|
|
|
205
227
|
if result.returncode != 0:
|
|
206
228
|
return False, f"smart-install.sh failed in {plugin_root}"
|
|
207
229
|
if _INSTALL_FAILURE_MARKER.is_file():
|
|
208
|
-
|
|
230
|
+
first_line = _INSTALL_FAILURE_MARKER.read_text().splitlines()
|
|
231
|
+
reason = (first_line[0].strip() if first_line else "") or "unknown error"
|
|
209
232
|
return False, reason
|
|
210
233
|
return True, str(plugin_root)
|
|
211
234
|
|
|
@@ -734,7 +757,26 @@ def _register_codex_marketplace(root: Path) -> tuple[bool, str]:
|
|
|
734
757
|
return False, output or "Codex CLI does not expose plugin marketplace commands"
|
|
735
758
|
|
|
736
759
|
|
|
737
|
-
def
|
|
760
|
+
def _configure_reflexio_setup(args: argparse.Namespace) -> None:
|
|
761
|
+
api_key = (getattr(args, "api_key", "") or "").strip()
|
|
762
|
+
if api_key:
|
|
763
|
+
reflexio_url = (
|
|
764
|
+
getattr(args, "reflexio_url", "") or _MANAGED_REFLEXIO_URL
|
|
765
|
+
).strip()
|
|
766
|
+
added = _seed_managed_reflexio_env(api_key=api_key, reflexio_url=reflexio_url)
|
|
767
|
+
changed = ", ".join(added) if added else "managed Reflexio settings"
|
|
768
|
+
sys.stdout.write(
|
|
769
|
+
f"Configured {_REFLEXIO_ENV_PATH} for managed Reflexio "
|
|
770
|
+
f"({changed}; API key {env_config.mask_secret(api_key)}).\n"
|
|
771
|
+
)
|
|
772
|
+
return
|
|
773
|
+
|
|
774
|
+
added = _seed_reflexio_env()
|
|
775
|
+
if added:
|
|
776
|
+
sys.stdout.write(f"Seeded {_REFLEXIO_ENV_PATH} with {', '.join(added)}.\n")
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def cmd_install_codex(args: argparse.Namespace) -> int:
|
|
738
780
|
"""Install the claude-smart plugin marketplace for Codex.
|
|
739
781
|
|
|
740
782
|
Only supported from a source checkout — the wheel ships the Python
|
|
@@ -766,9 +808,7 @@ def cmd_install_codex(_args: argparse.Namespace) -> int:
|
|
|
766
808
|
return 1
|
|
767
809
|
marketplace_root = _prepare_codex_local_marketplace()
|
|
768
810
|
|
|
769
|
-
|
|
770
|
-
if added:
|
|
771
|
-
sys.stdout.write(f"Seeded {_REFLEXIO_ENV_PATH} with {', '.join(added)}.\n")
|
|
811
|
+
_configure_reflexio_setup(args)
|
|
772
812
|
|
|
773
813
|
hooks_ok, hooks_msg = _enable_codex_plugin_hooks()
|
|
774
814
|
if hooks_ok:
|
|
@@ -870,9 +910,7 @@ def cmd_install(args: argparse.Namespace) -> int:
|
|
|
870
910
|
sys.stderr.write(f"error: {' '.join(cmd)} failed (exit {exc.returncode})\n")
|
|
871
911
|
return exc.returncode or 1
|
|
872
912
|
|
|
873
|
-
|
|
874
|
-
if added:
|
|
875
|
-
sys.stdout.write(f"Seeded {_REFLEXIO_ENV_PATH} with {', '.join(added)}.\n")
|
|
913
|
+
_configure_reflexio_setup(args)
|
|
876
914
|
|
|
877
915
|
bootstrapped, message = _bootstrap_claude_code_install()
|
|
878
916
|
if not bootstrapped:
|
|
@@ -952,8 +990,7 @@ def cmd_uninstall(_args: argparse.Namespace) -> int:
|
|
|
952
990
|
|
|
953
991
|
sys.stdout.write(
|
|
954
992
|
"\nclaude-smart uninstalled. Restart Claude Code to apply.\n"
|
|
955
|
-
"
|
|
956
|
-
"remove manually if desired.\n"
|
|
993
|
+
f"{_LOCAL_DATA_NOTICE}"
|
|
957
994
|
)
|
|
958
995
|
return 0
|
|
959
996
|
|
|
@@ -990,8 +1027,8 @@ def cmd_uninstall_codex(_args: argparse.Namespace) -> int:
|
|
|
990
1027
|
sys.stderr.write(f"warning: could not update {_CODEX_CONFIG_PATH}\n")
|
|
991
1028
|
sys.stdout.write(
|
|
992
1029
|
"claude-smart Codex plugin and marketplace state removed. Restart Codex to apply. "
|
|
993
|
-
"Codex's global hook feature flags
|
|
994
|
-
"
|
|
1030
|
+
"Codex's global hook feature flags were left in place.\n"
|
|
1031
|
+
f"{_LOCAL_DATA_NOTICE}"
|
|
995
1032
|
)
|
|
996
1033
|
return 0
|
|
997
1034
|
|
|
@@ -1025,6 +1062,13 @@ def cmd_show(args: argparse.Namespace) -> int:
|
|
|
1025
1062
|
agent_playbooks=agent_playbooks,
|
|
1026
1063
|
profiles=profiles,
|
|
1027
1064
|
)
|
|
1065
|
+
if not md and adapter.read_errors:
|
|
1066
|
+
sys.stdout.write(
|
|
1067
|
+
"Could not read skills or preferences from Reflexio for "
|
|
1068
|
+
f"project `{project_id}` at `{adapter.url}`.\n"
|
|
1069
|
+
f"First error: {adapter.read_errors[0]}\n"
|
|
1070
|
+
)
|
|
1071
|
+
return 1
|
|
1028
1072
|
sys.stdout.write(
|
|
1029
1073
|
md or f"_No skills or preferences yet for project `{project_id}`._\n"
|
|
1030
1074
|
)
|
|
@@ -1034,8 +1078,10 @@ def cmd_show(args: argparse.Namespace) -> int:
|
|
|
1034
1078
|
def cmd_learn(args: argparse.Namespace) -> int:
|
|
1035
1079
|
"""Force reflexio extraction over the active session's interactions.
|
|
1036
1080
|
|
|
1037
|
-
Publishes unpublished interactions with ``force_extraction=True``
|
|
1038
|
-
|
|
1081
|
+
Publishes unpublished interactions with ``force_extraction=True`` and
|
|
1082
|
+
``override_learning_stall=True`` as an explicit retry request rather than
|
|
1083
|
+
waiting for the next batch interval. The publish call itself remains
|
|
1084
|
+
non-blocking: ``Adapter.publish`` sends ``wait_for_response=False``.
|
|
1039
1085
|
When ``args.note`` is provided, the note is appended to the session
|
|
1040
1086
|
buffer as a neutral User turn before publishing — letting the user
|
|
1041
1087
|
capture an explicit insight, preference, or workflow note alongside
|
|
@@ -1073,6 +1119,7 @@ def cmd_learn(args: argparse.Namespace) -> int:
|
|
|
1073
1119
|
session_id=session_id,
|
|
1074
1120
|
project_id=project_id,
|
|
1075
1121
|
force_extraction=True,
|
|
1122
|
+
override_learning_stall=True,
|
|
1076
1123
|
skip_aggregation=False,
|
|
1077
1124
|
)
|
|
1078
1125
|
if status == "ok":
|
|
@@ -1561,6 +1608,113 @@ def cmd_clear_all(args: argparse.Namespace) -> int:
|
|
|
1561
1608
|
return start_rc or 0
|
|
1562
1609
|
|
|
1563
1610
|
|
|
1611
|
+
def _resolve_reflexio_url() -> str:
|
|
1612
|
+
"""Return the URL endpoint for the local claude-smart reflexio backend.
|
|
1613
|
+
|
|
1614
|
+
Mirrors ``claude_smart.reflexio_adapter`` so the CLI's one-shot
|
|
1615
|
+
ReflexioClient calls hit the same server as the long-lived adapter
|
|
1616
|
+
used by hook handlers.
|
|
1617
|
+
|
|
1618
|
+
Returns:
|
|
1619
|
+
str: The ``REFLEXIO_URL`` env var if set, otherwise the default
|
|
1620
|
+
local backend endpoint.
|
|
1621
|
+
"""
|
|
1622
|
+
env_config.load_reflexio_env()
|
|
1623
|
+
return os.environ.get("REFLEXIO_URL", "http://localhost:8071/")
|
|
1624
|
+
|
|
1625
|
+
|
|
1626
|
+
def _resolve_reflexio_api_key() -> str:
|
|
1627
|
+
env_config.load_reflexio_env()
|
|
1628
|
+
return os.environ.get("REFLEXIO_API_KEY", "")
|
|
1629
|
+
|
|
1630
|
+
|
|
1631
|
+
def _format_deleted_counts(deleted_counts: object) -> str:
|
|
1632
|
+
"""Render ``deleted_counts`` from a ``clear_user_data`` response as a summary.
|
|
1633
|
+
|
|
1634
|
+
The backend returns one count per affected table. We surface the
|
|
1635
|
+
total in the headline string (``"removed N row(s)"``) and append a
|
|
1636
|
+
per-table breakdown when at least one bucket is populated, so the
|
|
1637
|
+
operator can tell *what* got cleared at a glance.
|
|
1638
|
+
|
|
1639
|
+
Args:
|
|
1640
|
+
deleted_counts (object): The ``deleted_counts`` field from the
|
|
1641
|
+
backend response. Expected to be a mapping of
|
|
1642
|
+
``str -> int``; anything else is rendered as ``"unknown"``.
|
|
1643
|
+
|
|
1644
|
+
Returns:
|
|
1645
|
+
str: A short human-readable summary suitable for echoing to
|
|
1646
|
+
stdout (e.g. ``"3 row(s) (interactions=3)"``).
|
|
1647
|
+
"""
|
|
1648
|
+
if not isinstance(deleted_counts, dict):
|
|
1649
|
+
return "unknown row(s)"
|
|
1650
|
+
pairs: list[tuple[str, int]] = []
|
|
1651
|
+
for key, value in deleted_counts.items():
|
|
1652
|
+
if isinstance(key, str) and isinstance(value, int):
|
|
1653
|
+
pairs.append((key, value))
|
|
1654
|
+
total = sum(value for _, value in pairs)
|
|
1655
|
+
if not pairs:
|
|
1656
|
+
return f"{total} row(s)"
|
|
1657
|
+
breakdown = ", ".join(f"{name}={count}" for name, count in sorted(pairs))
|
|
1658
|
+
return f"{total} row(s) ({breakdown})"
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
def cmd_clear_user(args: argparse.Namespace) -> int:
|
|
1662
|
+
"""Delete one user_id's playbooks, profiles, and interactions via the backend.
|
|
1663
|
+
|
|
1664
|
+
Per-user-scoped counterpart of ``clear-all``. Where ``clear-all`` does
|
|
1665
|
+
a filesystem-level wipe of the entire local storage root, ``clear-user``
|
|
1666
|
+
routes through the running reflexio backend's ``clear_user_data``
|
|
1667
|
+
endpoint so it can scope the deletion to a single ``user_id`` —
|
|
1668
|
+
leaving other users' data and globally-shared ``agent_playbooks``
|
|
1669
|
+
untouched. This is the primitive SWE-bench-style benchmark harnesses
|
|
1670
|
+
need when running many parallel evaluations under distinct synthetic
|
|
1671
|
+
user ids against one shared backend.
|
|
1672
|
+
|
|
1673
|
+
Args:
|
|
1674
|
+
args (argparse.Namespace): Parsed CLI args. Requires
|
|
1675
|
+
``args.user_id`` (positional) and ``args.yes`` (the
|
|
1676
|
+
destructive-action confirmation flag, matching ``clear-all``).
|
|
1677
|
+
|
|
1678
|
+
Returns:
|
|
1679
|
+
int: 0 on success, 1 if confirmation is missing or the backend
|
|
1680
|
+
call fails.
|
|
1681
|
+
"""
|
|
1682
|
+
user_id = args.user_id
|
|
1683
|
+
if not args.yes:
|
|
1684
|
+
sys.stdout.write(
|
|
1685
|
+
f"This will permanently delete all reflexio rows for user '{user_id}' "
|
|
1686
|
+
"(playbooks, profiles, interactions). Other users' data and "
|
|
1687
|
+
"agent_playbooks are not affected.\n"
|
|
1688
|
+
"Re-run with --yes to confirm.\n"
|
|
1689
|
+
)
|
|
1690
|
+
return 1
|
|
1691
|
+
|
|
1692
|
+
try:
|
|
1693
|
+
from reflexio import ReflexioClient # type: ignore[import-not-found]
|
|
1694
|
+
except ImportError as exc:
|
|
1695
|
+
sys.stderr.write(f"error: reflexio is not installed: {exc}\n")
|
|
1696
|
+
return 1
|
|
1697
|
+
|
|
1698
|
+
try:
|
|
1699
|
+
client = ReflexioClient(
|
|
1700
|
+
url_endpoint=_resolve_reflexio_url(),
|
|
1701
|
+
api_key=_resolve_reflexio_api_key(),
|
|
1702
|
+
)
|
|
1703
|
+
# The companion reflexio PR adds ``clear_user_data``; keep the
|
|
1704
|
+
# CLI buildable until both sides ship together.
|
|
1705
|
+
response = client.clear_user_data(user_id) # pyright: ignore[reportAttributeAccessIssue]
|
|
1706
|
+
except Exception as exc: # noqa: BLE001 — surface backend failure verbatim.
|
|
1707
|
+
sys.stderr.write(f"error: clear-user failed: {exc}\n")
|
|
1708
|
+
return 1
|
|
1709
|
+
|
|
1710
|
+
deleted_counts = (
|
|
1711
|
+
response.get("deleted_counts") if isinstance(response, dict) else None
|
|
1712
|
+
)
|
|
1713
|
+
summary = _format_deleted_counts(deleted_counts)
|
|
1714
|
+
sys.stdout.write(f"Cleared user '{user_id}': removed {summary}.\n")
|
|
1715
|
+
return 0
|
|
1716
|
+
|
|
1717
|
+
|
|
1564
1718
|
def _build_parser() -> argparse.ArgumentParser:
|
|
1565
1719
|
parser = argparse.ArgumentParser(prog="claude-smart")
|
|
1566
1720
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
@@ -1577,6 +1731,16 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1577
1731
|
default=_DEFAULT_MARKETPLACE_SOURCE,
|
|
1578
1732
|
help="Marketplace ref — GitHub owner/repo, or a local directory path",
|
|
1579
1733
|
)
|
|
1734
|
+
inst.add_argument(
|
|
1735
|
+
"--api-key",
|
|
1736
|
+
default="",
|
|
1737
|
+
help="Configure managed Reflexio with this API key instead of local mode",
|
|
1738
|
+
)
|
|
1739
|
+
inst.add_argument(
|
|
1740
|
+
"--reflexio-url",
|
|
1741
|
+
default=_MANAGED_REFLEXIO_URL,
|
|
1742
|
+
help="Managed Reflexio URL used only when --api-key is provided",
|
|
1743
|
+
)
|
|
1580
1744
|
inst.set_defaults(func=cmd_install)
|
|
1581
1745
|
|
|
1582
1746
|
upd = sub.add_parser("update", help="Update claude-smart to the latest version")
|
|
@@ -1625,6 +1789,25 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1625
1789
|
)
|
|
1626
1790
|
ca.set_defaults(func=cmd_clear_all)
|
|
1627
1791
|
|
|
1792
|
+
cu = sub.add_parser(
|
|
1793
|
+
"clear-user",
|
|
1794
|
+
help=(
|
|
1795
|
+
"Delete one user_id's playbooks, profiles, and interactions via "
|
|
1796
|
+
"the backend (leaves other users and agent_playbooks intact)"
|
|
1797
|
+
),
|
|
1798
|
+
)
|
|
1799
|
+
cu.add_argument(
|
|
1800
|
+
"user_id",
|
|
1801
|
+
help="User id whose reflexio rows should be cleared",
|
|
1802
|
+
)
|
|
1803
|
+
cu.add_argument(
|
|
1804
|
+
"--yes",
|
|
1805
|
+
"-y",
|
|
1806
|
+
action="store_true",
|
|
1807
|
+
help="Confirm the destructive clear without prompting",
|
|
1808
|
+
)
|
|
1809
|
+
cu.set_defaults(func=cmd_clear_user)
|
|
1810
|
+
|
|
1628
1811
|
rs = sub.add_parser(
|
|
1629
1812
|
"restart",
|
|
1630
1813
|
help="Restart the reflexio backend and dashboard to pick up changes",
|
|
@@ -1649,6 +1832,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1649
1832
|
|
|
1650
1833
|
|
|
1651
1834
|
def main(argv: list[str] | None = None) -> int:
|
|
1835
|
+
env_config.load_reflexio_env()
|
|
1652
1836
|
parser = _build_parser()
|
|
1653
1837
|
args = parser.parse_args(argv)
|
|
1654
1838
|
return args.func(args)
|