claude-smart 0.2.29 → 0.2.31
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 +9 -23
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/hooks/codex-hooks.json +3 -3
- package/plugin/hooks/hooks.json +4 -4
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +65 -0
- package/plugin/scripts/backend-service.sh +29 -7
- package/plugin/scripts/cli.sh +3 -2
- package/plugin/scripts/codex-hook.js +72 -9
- package/plugin/scripts/dashboard-service.sh +16 -4
- package/plugin/scripts/hook_entry.sh +50 -9
- package/plugin/scripts/smart-install.sh +57 -39
- package/plugin/src/claude_smart/cli.py +119 -1
- 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 +17 -4
- package/plugin/src/claude_smart/hook.py +62 -4
- package/plugin/src/claude_smart/hook_log.py +301 -0
- package/plugin/uv.lock +1 -1
package/README.md
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
claude-smart
|
|
7
7
|
</h1>
|
|
8
8
|
|
|
9
|
-
<h4 align="center">A
|
|
9
|
+
<h4 align="center">A self-improvement plugin for <a href="https://claude.com/claude-code" target="_blank">Claude Code</a> and Codex that turns interactions into durable skills they follow in future sessions.</h4>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="LICENSE">
|
|
13
13
|
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
|
|
14
14
|
</a>
|
|
15
15
|
<a href="plugin/pyproject.toml">
|
|
16
|
-
<img src="https://img.shields.io/badge/version-0.2.
|
|
16
|
+
<img src="https://img.shields.io/badge/version-0.2.31-green.svg" alt="Version">
|
|
17
17
|
</a>
|
|
18
18
|
<a href="plugin/pyproject.toml">
|
|
19
19
|
<img src="https://img.shields.io/badge/python-%3E%3D3.12-brightgreen.svg" alt="Python">
|
|
@@ -24,9 +24,6 @@
|
|
|
24
24
|
<a href="#quick-start">
|
|
25
25
|
<img src="https://img.shields.io/badge/hosts-Claude%20Code%20%2B%20Codex-purple.svg" alt="Hosts">
|
|
26
26
|
</a>
|
|
27
|
-
<a href="https://discord.gg/Jbft3jPn">
|
|
28
|
-
<img src="https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white" alt="Discord">
|
|
29
|
-
</a>
|
|
30
27
|
</p>
|
|
31
28
|
|
|
32
29
|
<p align="center">
|
|
@@ -40,22 +37,22 @@
|
|
|
40
37
|
</p>
|
|
41
38
|
|
|
42
39
|
<p align="center">
|
|
43
|
-
|
|
40
|
+
claude-smart learns from your corrections <i>and</i> from what's already working — so the assistant stops repeating mistakes and reuses proven paths. Project-specific skills capture repo-local rules, and shared skills roll up durable patterns for reuse across projects.
|
|
44
41
|
</p>
|
|
45
42
|
|
|
46
43
|
<p align="center">
|
|
47
|
-
<b>vs <code>claude-mem</code>:</b> ~3× better at turning your corrections into rules Claude follows, ~50% more of
|
|
44
|
+
<b>vs <code>claude-mem</code>:</b> ~3× better at turning your corrections into rules Claude follows, ~50% more of your guidance is retained. <a href="EXPERIMENT.md"><b>Read the benchmark →</b></a>
|
|
48
45
|
</p>
|
|
49
46
|
|
|
50
47
|
---
|
|
51
48
|
|
|
52
49
|
## Why Learning, Not Memory
|
|
53
50
|
|
|
54
|
-
Most memory
|
|
51
|
+
Most memory tools just record. Claude remembers what happened, but doesn't change what it does next.
|
|
55
52
|
|
|
56
53
|
`claude-smart` focuses on learning instead.
|
|
57
54
|
|
|
58
|
-
Four
|
|
55
|
+
Four things this changes:
|
|
59
56
|
|
|
60
57
|
- 💡 **Stop repeating the same mistakes:** Produces actionable skills Claude can follow next time; memory only records what happened.
|
|
61
58
|
|
|
@@ -63,12 +60,12 @@ Four ways this changes what your coding assistant can do for you:
|
|
|
63
60
|
> **Memory:** “deploy broke after prisma bump; user rolled back”<br>
|
|
64
61
|
> **Learning:** “treat major-version bumps of ORMs/DB drivers as breaking — verify with integration tests, not just unit tests”
|
|
65
62
|
|
|
66
|
-
- 🚀 **Start from the optimized path:**
|
|
63
|
+
- 🚀 **Start from the optimized path:** Records and optimizes the steps that worked so Claude can reuse them, instead of re-exploring each session.
|
|
67
64
|
> *Example:* Claude spends several iterations trying to start the local dev environment before discovering that this repo requires `pnpm dev:all` instead of the usual `npm run dev`.<br>
|
|
68
65
|
> **Memory:** “user mentioned that `npm run dev` did not work”<br>
|
|
69
66
|
> **Learning:** “for this repo, always use `pnpm dev:all` to start the full local stack — `npm run dev` only starts the frontend and causes missing service errors”
|
|
70
67
|
|
|
71
|
-
Instead of re-exploring, Claude starts from the proven path—reducing planning steps, latency, and token usage.
|
|
68
|
+
Instead of re-exploring, Claude Code starts from the proven path—reducing planning steps, latency, and token usage.
|
|
72
69
|
|
|
73
70
|
- 🌐 **Project-specific and shared skills:** Session memory disappears with the conversation. Project-specific skills preserve repo-local rules, while shared skills roll up patterns that should transfer across projects. Preferences stay scoped to the current project so per-repo preferences don't leak across projects.
|
|
74
71
|
|
|
@@ -95,12 +92,6 @@ claude plugin marketplace add ReflexioAI/claude-smart
|
|
|
95
92
|
claude plugin install claude-smart@reflexioai
|
|
96
93
|
```
|
|
97
94
|
|
|
98
|
-
The plugin Setup hook installs its own `uv`, Python 3.12 environment, and
|
|
99
|
-
private Node.js/npm runtime under `~/.claude-smart/` when they are missing, so
|
|
100
|
-
you do not need to install Python or Node globally for the plugin/dashboard.
|
|
101
|
-
On native Windows, Claude Code hooks still need a Git Bash-compatible `bash`
|
|
102
|
-
until Claude Code exposes a single cross-platform hook command shape.
|
|
103
|
-
|
|
104
95
|
To uninstall:
|
|
105
96
|
|
|
106
97
|
```bash
|
|
@@ -123,11 +114,6 @@ Then fully quit and reopen Codex so hooks reload.
|
|
|
123
114
|
|
|
124
115
|
Requires the `codex` CLI on `PATH` and Node.js (for `npx`).
|
|
125
116
|
|
|
126
|
-
The `npx` command needs Node.js only to launch the installer. The installed
|
|
127
|
-
plugin uses a private Node.js/npm runtime and a `uv`-managed Python 3.12
|
|
128
|
-
environment under `~/.claude-smart/`, so hooks and the dashboard do not depend
|
|
129
|
-
on global Python, uv, Node, or npm after install.
|
|
130
|
-
|
|
131
117
|
To uninstall:
|
|
132
118
|
|
|
133
119
|
```bash
|
|
@@ -170,7 +156,7 @@ the first local embedding model download. The ONNX model cache lives at
|
|
|
170
156
|
|
|
171
157
|
## Dashboard
|
|
172
158
|
|
|
173
|
-
A web UI for browsing session histories, inspecting preferences, and editing project-specific and shared skills. The dashboard auto-starts alongside the backend, so you can open **http://localhost:3001** directly. Or run `/claude-smart:dashboard` in Claude Code to
|
|
159
|
+
A web UI for browsing session histories, inspecting preferences, and editing project-specific and shared skills. The dashboard auto-starts alongside the backend, so you can open **http://localhost:3001** directly. Or run `/claude-smart:dashboard` in Claude Code to open it in your browser. In Codex, run `bash ~/.reflexio/plugin-root/scripts/dashboard-open.sh`.
|
|
174
160
|
|
|
175
161
|
<p align="center">
|
|
176
162
|
<img src="assets/preferences_dashboard.png" alt="Preferences dashboard" width="49%">
|
package/package.json
CHANGED
|
@@ -18,17 +18,17 @@
|
|
|
18
18
|
{
|
|
19
19
|
"type": "command",
|
|
20
20
|
"command": "_R=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -z \"$_R\" ] && _R=$(ls -dt \"$HOME/.codex/plugins/cache/reflexioai/claude-smart\"/*/ 2>/dev/null | head -n 1); [ -n \"$_R\" ] && . \"${_R%/}/scripts/_codex_env.sh\" && bash \"$_R/scripts/backend-service.sh\" start",
|
|
21
|
-
"timeout":
|
|
21
|
+
"timeout": 300
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"type": "command",
|
|
25
25
|
"command": "_R=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -z \"$_R\" ] && _R=$(ls -dt \"$HOME/.codex/plugins/cache/reflexioai/claude-smart\"/*/ 2>/dev/null | head -n 1); [ -n \"$_R\" ] && . \"${_R%/}/scripts/_codex_env.sh\" && bash \"$_R/scripts/dashboard-service.sh\" start",
|
|
26
|
-
"timeout":
|
|
26
|
+
"timeout": 300
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"type": "command",
|
|
30
30
|
"command": "_R=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -z \"$_R\" ] && _R=$(ls -dt \"$HOME/.codex/plugins/cache/reflexioai/claude-smart\"/*/ 2>/dev/null | head -n 1); [ -n \"$_R\" ] && . \"${_R%/}/scripts/_codex_env.sh\" && bash \"$_R/scripts/hook_entry.sh\" codex session-start",
|
|
31
|
-
"timeout":
|
|
31
|
+
"timeout": 300,
|
|
32
32
|
"statusMessage": "Loading claude-smart context"
|
|
33
33
|
}
|
|
34
34
|
]
|
package/plugin/hooks/hooks.json
CHANGED
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
{
|
|
36
36
|
"type": "command",
|
|
37
37
|
"command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/hook_entry.sh\" claude-code session-start",
|
|
38
|
-
"timeout":
|
|
38
|
+
"timeout": 300
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"type": "command",
|
|
42
42
|
"command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/backend-service.sh\" start",
|
|
43
|
-
"timeout":
|
|
43
|
+
"timeout": 300
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
"type": "command",
|
|
47
47
|
"command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/dashboard-service.sh\" start",
|
|
48
|
-
"timeout":
|
|
48
|
+
"timeout": 300
|
|
49
49
|
}
|
|
50
50
|
]
|
|
51
51
|
}
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
{
|
|
103
103
|
"type": "command",
|
|
104
104
|
"command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/hook_entry.sh\" claude-code session-end",
|
|
105
|
-
"timeout":
|
|
105
|
+
"timeout": 300
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
108
|
"type": "command",
|
package/plugin/pyproject.toml
CHANGED
package/plugin/scripts/_lib.sh
CHANGED
|
@@ -32,6 +32,16 @@ claude_smart_prepend_node_bins() {
|
|
|
32
32
|
export PATH="$_CS_NODE_ROOT/bin:$_CS_NODE_ROOT:$PATH"
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
claude_smart_is_internal_invocation_env() {
|
|
36
|
+
if [ "${CLAUDE_SMART_INTERNAL:-}" = "1" ]; then
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
if [ -n "${CLAUDE_CODE_ENTRYPOINT:-}" ] && [ "${CLAUDE_CODE_ENTRYPOINT:-}" != "cli" ]; then
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
claude_smart_dashboard_unavailable_marker() {
|
|
36
46
|
printf '%s\n' "$HOME/.claude-smart/dashboard-unavailable"
|
|
37
47
|
}
|
|
@@ -196,6 +206,61 @@ PY
|
|
|
196
206
|
return 1
|
|
197
207
|
}
|
|
198
208
|
|
|
209
|
+
# Print a short fingerprint line for a file ("<cksum>:<size>" or "missing").
|
|
210
|
+
claude_smart_fingerprint_file() {
|
|
211
|
+
local path
|
|
212
|
+
path="$1"
|
|
213
|
+
if [ -f "$path" ]; then
|
|
214
|
+
cksum "$path" 2>/dev/null | awk '{print $1 ":" $2}'
|
|
215
|
+
else
|
|
216
|
+
printf 'missing\n'
|
|
217
|
+
fi
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Print a multi-line fingerprint covering every input that determines
|
|
221
|
+
# whether smart-install.sh can succeed. Order is stable so the hash is
|
|
222
|
+
# reproducible. Args: $1 = plugin_root, $2 = scripts_dir.
|
|
223
|
+
claude_smart_install_fingerprint() {
|
|
224
|
+
local plugin_root script_dir
|
|
225
|
+
plugin_root="$1"
|
|
226
|
+
script_dir="$2"
|
|
227
|
+
printf 'plugin_root=%s\n' "$plugin_root"
|
|
228
|
+
printf 'smart_install=%s\n' "$(claude_smart_fingerprint_file "$script_dir/smart-install.sh")"
|
|
229
|
+
printf 'lib=%s\n' "$(claude_smart_fingerprint_file "$script_dir/_lib.sh")"
|
|
230
|
+
printf 'pyproject=%s\n' "$(claude_smart_fingerprint_file "$plugin_root/pyproject.toml")"
|
|
231
|
+
printf 'uv_lock=%s\n' "$(claude_smart_fingerprint_file "$plugin_root/uv.lock")"
|
|
232
|
+
# Resolved python interpreter — catches a system upgrade (3.12.4 → 3.12.5)
|
|
233
|
+
# that would otherwise let install_complete return true against a venv
|
|
234
|
+
# built against a now-deleted interpreter.
|
|
235
|
+
if command -v uv >/dev/null 2>&1; then
|
|
236
|
+
printf 'python=%s\n' "$(uv python find 3.12 2>/dev/null || echo missing)"
|
|
237
|
+
else
|
|
238
|
+
printf 'python=no-uv\n'
|
|
239
|
+
fi
|
|
240
|
+
if [ -d "$plugin_root/dashboard" ]; then
|
|
241
|
+
printf 'dashboard_pkg=%s\n' "$(claude_smart_fingerprint_file "$plugin_root/dashboard/package.json")"
|
|
242
|
+
printf 'dashboard_lock=%s\n' "$(claude_smart_fingerprint_file "$plugin_root/dashboard/package-lock.json")"
|
|
243
|
+
else
|
|
244
|
+
printf 'dashboard_pkg=none\n'
|
|
245
|
+
printf 'dashboard_lock=none\n'
|
|
246
|
+
fi
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# Hash the install fingerprint to a single short hex string. Used by the
|
|
250
|
+
# install-failed marker so hook_entry.sh can detect when inputs change and
|
|
251
|
+
# auto-clear a stale marker. Args: $1 = plugin_root, $2 = scripts_dir.
|
|
252
|
+
claude_smart_install_fingerprint_hash() {
|
|
253
|
+
local plugin_root script_dir tmp rc
|
|
254
|
+
plugin_root="$1"
|
|
255
|
+
script_dir="$2"
|
|
256
|
+
tmp=$(mktemp 2>/dev/null) || return 1
|
|
257
|
+
claude_smart_install_fingerprint "$plugin_root" "$script_dir" > "$tmp"
|
|
258
|
+
claude_smart_sha256_file "$tmp"
|
|
259
|
+
rc=$?
|
|
260
|
+
rm -f "$tmp"
|
|
261
|
+
return $rc
|
|
262
|
+
}
|
|
263
|
+
|
|
199
264
|
# Return 0 if `node` exists and satisfies the minimum major/minor pair.
|
|
200
265
|
# Patch versions are intentionally ignored because our requirement is a
|
|
201
266
|
# floor, not an exact runtime pin.
|
|
@@ -25,15 +25,21 @@ claude_smart_prepend_astral_bins
|
|
|
25
25
|
|
|
26
26
|
CMD="${1:-start}"
|
|
27
27
|
PORT=8071
|
|
28
|
+
EMBEDDING_PORT="${EMBEDDING_PORT:-8072}"
|
|
28
29
|
# Pass through to `reflexio services start/stop` so the spawned backend
|
|
29
30
|
# binds to PORT instead of reflexio's library default (8081).
|
|
30
31
|
export BACKEND_PORT="$PORT"
|
|
32
|
+
export EMBEDDING_PORT
|
|
31
33
|
|
|
32
34
|
# Default: route extraction through the active host CLI + ONNX embedder
|
|
33
35
|
# so claude-smart works without any LLM API key. Users can opt out by
|
|
34
36
|
# pre-exporting these to 0.
|
|
35
37
|
export CLAUDE_SMART_USE_LOCAL_CLI="${CLAUDE_SMART_USE_LOCAL_CLI:-1}"
|
|
36
38
|
export CLAUDE_SMART_USE_LOCAL_EMBEDDING="${CLAUDE_SMART_USE_LOCAL_EMBEDDING:-1}"
|
|
39
|
+
if [ "${CLAUDE_SMART_USE_LOCAL_EMBEDDING:-}" = "1" ]; then
|
|
40
|
+
export REFLEXIO_EMBEDDING_PROVIDER="${REFLEXIO_EMBEDDING_PROVIDER:-local_service}"
|
|
41
|
+
export REFLEXIO_EMBEDDING_SERVICE_URL="${REFLEXIO_EMBEDDING_SERVICE_URL:-http://127.0.0.1:$EMBEDDING_PORT}"
|
|
42
|
+
fi
|
|
37
43
|
# The backend can be spawned from contexts whose PATH lacks the host
|
|
38
44
|
# CLI dir (commonly ~/.local/bin or /opt/homebrew/bin). Pin the CLI
|
|
39
45
|
# explicitly if we can resolve it from our own (post-login-path) PATH.
|
|
@@ -173,6 +179,9 @@ full_stop() {
|
|
|
173
179
|
|
|
174
180
|
case "$CMD" in
|
|
175
181
|
start)
|
|
182
|
+
if claude_smart_is_internal_invocation_env; then
|
|
183
|
+
emit_ok; exit 0
|
|
184
|
+
fi
|
|
176
185
|
# Opt-out: users who don't want the backend managed by the hook can
|
|
177
186
|
# set CLAUDE_SMART_BACKEND_AUTOSTART=0.
|
|
178
187
|
if [ "${CLAUDE_SMART_BACKEND_AUTOSTART:-1}" = "0" ]; then
|
|
@@ -186,8 +195,16 @@ case "$CMD" in
|
|
|
186
195
|
emit_ok; exit 0
|
|
187
196
|
fi
|
|
188
197
|
if ! command -v uv >/dev/null 2>&1; then
|
|
189
|
-
|
|
190
|
-
|
|
198
|
+
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
199
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; running installer"
|
|
200
|
+
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >>"$STATE_DIR/install.log" 2>&1 || true
|
|
201
|
+
claude_smart_source_login_path
|
|
202
|
+
claude_smart_prepend_astral_bins
|
|
203
|
+
fi
|
|
204
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
205
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH after installer; skipping"
|
|
206
|
+
emit_ok; exit 0
|
|
207
|
+
fi
|
|
191
208
|
fi
|
|
192
209
|
cd "$PLUGIN_ROOT"
|
|
193
210
|
|
|
@@ -198,16 +215,21 @@ case "$CMD" in
|
|
|
198
215
|
export INTERACTION_CLEANUP_THRESHOLD="${INTERACTION_CLEANUP_THRESHOLD:-500}"
|
|
199
216
|
export INTERACTION_CLEANUP_DELETE_COUNT="${INTERACTION_CLEANUP_DELETE_COUNT:-200}"
|
|
200
217
|
|
|
201
|
-
# --no-reload: uvicorn's reloader forks a supervisor; makes
|
|
202
|
-
# bookkeeping harder and we don't need hot-reload for a user-facing
|
|
203
|
-
# service. Detach via claude_smart_spawn_detached so the same code
|
|
204
|
-
# path covers Linux (setsid), macOS (python3 os.setsid), and Windows
|
|
205
218
|
# (nohup; no process groups). backend-log-runner.sh owns stdout/stderr
|
|
206
219
|
# capture so process output cannot grow backend.log past its cap.
|
|
220
|
+
#
|
|
221
|
+
# --workers: reflexio defaults to 2 (zero-downtime worker recycling
|
|
222
|
+
# for server deployments). For a single-user Claude Code plugin
|
|
223
|
+
# that's pure overhead: ~1.1 GB extra RSS, periodic 5–10 s spawn
|
|
224
|
+
# hiccups during worker rotation, and SQLite can't accept concurrent
|
|
225
|
+
# writers anyway. Default to 1 here; opt in to N via
|
|
226
|
+
# CLAUDE_SMART_BACKEND_WORKERS for power users running concurrent
|
|
227
|
+
# Claude Code sessions or wanting zero-downtime recycling.
|
|
228
|
+
workers="${CLAUDE_SMART_BACKEND_WORKERS:-1}"
|
|
207
229
|
claude_smart_spawn_detached bash "$HERE/backend-log-runner.sh" \
|
|
208
230
|
"$LOG_FILE" "$LOG_MAX_BYTES" -- \
|
|
209
231
|
uv run --project "$PLUGIN_ROOT" --quiet \
|
|
210
|
-
reflexio services start --only backend --no-reload
|
|
232
|
+
reflexio services start --only backend --no-reload --workers "$workers"
|
|
211
233
|
svc_pid=$!
|
|
212
234
|
# Record the spawned pid, not a pgid sampled with ps. On POSIX,
|
|
213
235
|
# setsid/python os.setsid make this pid the new process group leader;
|
package/plugin/scripts/cli.sh
CHANGED
|
@@ -24,7 +24,7 @@ PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
|
24
24
|
# on a broken install.
|
|
25
25
|
FAILURE_MARKER="$HOME/.claude-smart/install-failed"
|
|
26
26
|
if [ -f "$FAILURE_MARKER" ]; then
|
|
27
|
-
msg="$(
|
|
27
|
+
msg="$(head -n 1 "$FAILURE_MARKER" 2>/dev/null || echo "")"
|
|
28
28
|
[ -n "$msg" ] || msg="unknown error"
|
|
29
29
|
echo "claude-smart is not installed correctly: $msg" >&2
|
|
30
30
|
echo "Re-run the plugin's Setup (restart Claude Code) or fix the underlying issue and delete $FAILURE_MARKER to retry." >&2
|
|
@@ -50,7 +50,8 @@ if ! command -v uv >/dev/null 2>&1; then
|
|
|
50
50
|
fi
|
|
51
51
|
if ! command -v uv >/dev/null 2>&1; then
|
|
52
52
|
if [ -f "$FAILURE_MARKER" ]; then
|
|
53
|
-
msg="$(
|
|
53
|
+
msg="$(head -n 1 "$FAILURE_MARKER" 2>/dev/null || echo "")"
|
|
54
|
+
[ -n "$msg" ] || msg="unknown error"
|
|
54
55
|
echo "claude-smart: install failed: $msg" >&2
|
|
55
56
|
echo "Fix the underlying issue and delete $FAILURE_MARKER to retry." >&2
|
|
56
57
|
else
|
|
@@ -104,6 +104,10 @@ function npmPath() {
|
|
|
104
104
|
return commandPath(process.platform === "win32" ? ["npm.cmd", "npm.exe", "npm"] : ["npm"]);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
function bashPath() {
|
|
108
|
+
return commandPath(process.platform === "win32" ? ["bash.exe", "bash"] : ["bash"]);
|
|
109
|
+
}
|
|
110
|
+
|
|
107
111
|
function stateFile(name) {
|
|
108
112
|
return path.join(STATE_DIR, name);
|
|
109
113
|
}
|
|
@@ -202,6 +206,49 @@ function detached(command, args, options = {}) {
|
|
|
202
206
|
return child.pid;
|
|
203
207
|
}
|
|
204
208
|
|
|
209
|
+
function runInstaller(root, reason) {
|
|
210
|
+
if (process.env.CLAUDE_SMART_BOOTSTRAPPING === "1") return false;
|
|
211
|
+
const script = path.join(root, "scripts", "smart-install.sh");
|
|
212
|
+
if (!fs.existsSync(script)) return false;
|
|
213
|
+
const bash = bashPath();
|
|
214
|
+
if (!bash) return false;
|
|
215
|
+
appendLog("backend.log", `[claude-smart] ${reason}; running installer`);
|
|
216
|
+
const result = spawnSync(bash, [script], {
|
|
217
|
+
cwd: root,
|
|
218
|
+
env: {
|
|
219
|
+
...process.env,
|
|
220
|
+
CLAUDE_SMART_BOOTSTRAPPING: "1",
|
|
221
|
+
},
|
|
222
|
+
encoding: "utf8",
|
|
223
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
224
|
+
windowsHide: true,
|
|
225
|
+
});
|
|
226
|
+
const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
227
|
+
if (output) {
|
|
228
|
+
ensureDir(STATE_DIR);
|
|
229
|
+
fs.appendFileSync(path.join(STATE_DIR, "install.log"), `${output}\n`);
|
|
230
|
+
trimLog(path.join(STATE_DIR, "install.log"));
|
|
231
|
+
}
|
|
232
|
+
prependRuntimePath();
|
|
233
|
+
return result.status === 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function startInstallerDetached(root, reason) {
|
|
237
|
+
if (process.env.CLAUDE_SMART_BOOTSTRAPPING === "1") return false;
|
|
238
|
+
const script = path.join(root, "scripts", "smart-install.sh");
|
|
239
|
+
const bash = bashPath();
|
|
240
|
+
if (!bash || !fs.existsSync(script)) return false;
|
|
241
|
+
appendLog("backend.log", `[claude-smart] ${reason}; starting installer in background`);
|
|
242
|
+
detached(bash, [script], {
|
|
243
|
+
cwd: root,
|
|
244
|
+
env: {
|
|
245
|
+
...process.env,
|
|
246
|
+
CLAUDE_SMART_BOOTSTRAPPING: "1",
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
|
|
205
252
|
function readPid(file) {
|
|
206
253
|
try {
|
|
207
254
|
const value = fs.readFileSync(file, "utf8").trim();
|
|
@@ -262,7 +309,11 @@ async function startBackend(root) {
|
|
|
262
309
|
}
|
|
263
310
|
const uv = uvPath();
|
|
264
311
|
if (!uv) {
|
|
265
|
-
|
|
312
|
+
runInstaller(root, "backend: uv not on PATH");
|
|
313
|
+
}
|
|
314
|
+
const readyUv = uvPath();
|
|
315
|
+
if (!readyUv) {
|
|
316
|
+
appendLog("backend.log", "[claude-smart] backend: uv not on PATH after installer; skipping");
|
|
266
317
|
emitOk();
|
|
267
318
|
return;
|
|
268
319
|
}
|
|
@@ -284,7 +335,7 @@ async function startBackend(root) {
|
|
|
284
335
|
INTERACTION_CLEANUP_DELETE_COUNT: process.env.INTERACTION_CLEANUP_DELETE_COUNT || "200",
|
|
285
336
|
};
|
|
286
337
|
const pid = detached(
|
|
287
|
-
|
|
338
|
+
readyUv,
|
|
288
339
|
[
|
|
289
340
|
"run",
|
|
290
341
|
"--project",
|
|
@@ -326,14 +377,18 @@ async function startDashboard(root) {
|
|
|
326
377
|
}
|
|
327
378
|
const npm = npmPath();
|
|
328
379
|
if (!npm) {
|
|
329
|
-
|
|
380
|
+
runInstaller(root, "dashboard: npm not on PATH");
|
|
381
|
+
}
|
|
382
|
+
const readyNpm = npmPath();
|
|
383
|
+
if (!readyNpm) {
|
|
384
|
+
appendLog("dashboard.log", "[claude-smart] dashboard: npm not on PATH after installer; skipping");
|
|
330
385
|
emitOk();
|
|
331
386
|
return;
|
|
332
387
|
}
|
|
333
388
|
if (!fs.existsSync(path.join(dashboard, ".next"))) {
|
|
334
389
|
const buildPidFile = path.join(STATE_DIR, "dashboard-build.pid");
|
|
335
390
|
if (!pidAlive(readPid(buildPidFile))) {
|
|
336
|
-
const pid = detached(
|
|
391
|
+
const pid = detached(readyNpm, ["run", "build"], { cwd: dashboard });
|
|
337
392
|
writePid(buildPidFile, pid);
|
|
338
393
|
appendLog("dashboard.log", "[claude-smart] dashboard: .next missing; started background build");
|
|
339
394
|
}
|
|
@@ -346,7 +401,7 @@ async function startDashboard(root) {
|
|
|
346
401
|
REFLEXIO_URL: readBackendUrl(),
|
|
347
402
|
CLAUDE_SMART_DASHBOARD_WORKSPACE: process.cwd(),
|
|
348
403
|
};
|
|
349
|
-
const pid = detached(
|
|
404
|
+
const pid = detached(readyNpm, ["run", "start"], { cwd: dashboard, env });
|
|
350
405
|
writePid(pidFile, pid);
|
|
351
406
|
await waitForHealth(DASHBOARD_PORT, "/api/health", "x-claude-smart-dashboard", 5);
|
|
352
407
|
emitOk();
|
|
@@ -354,11 +409,19 @@ async function startDashboard(root) {
|
|
|
354
409
|
|
|
355
410
|
function runHook(root, event) {
|
|
356
411
|
trimLog(path.join(STATE_DIR, "backend.log"));
|
|
357
|
-
|
|
412
|
+
let uv = uvPath();
|
|
358
413
|
if (!uv) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
414
|
+
if (event === "session-start") {
|
|
415
|
+
runInstaller(root, "hook: uv not on PATH");
|
|
416
|
+
uv = uvPath();
|
|
417
|
+
} else {
|
|
418
|
+
startInstallerDetached(root, "hook: uv not on PATH");
|
|
419
|
+
}
|
|
420
|
+
if (!uv) {
|
|
421
|
+
appendLog("backend.log", "[claude-smart] hook: uv not on PATH after installer; skipping");
|
|
422
|
+
emitOk();
|
|
423
|
+
return 0;
|
|
424
|
+
}
|
|
362
425
|
}
|
|
363
426
|
const input = fs.readFileSync(0);
|
|
364
427
|
const result = spawnSync(
|
|
@@ -87,6 +87,9 @@ port_occupied() {
|
|
|
87
87
|
|
|
88
88
|
case "$CMD" in
|
|
89
89
|
start)
|
|
90
|
+
if claude_smart_is_internal_invocation_env; then
|
|
91
|
+
emit_ok; exit 0
|
|
92
|
+
fi
|
|
90
93
|
# Opt-out: users who don't want the dashboard long-lived can set
|
|
91
94
|
# CLAUDE_SMART_DASHBOARD_AUTOSTART=0 in their environment.
|
|
92
95
|
if [ "${CLAUDE_SMART_DASHBOARD_AUTOSTART:-1}" = "0" ]; then
|
|
@@ -100,10 +103,19 @@ case "$CMD" in
|
|
|
100
103
|
fi
|
|
101
104
|
NPM_BIN=$(claude_smart_resolve_npm || true)
|
|
102
105
|
if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
107
|
+
echo "[claude-smart] dashboard: npm is not on PATH; running installer" >>"$LOG_FILE"
|
|
108
|
+
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >>"$STATE_DIR/install.log" 2>&1 || true
|
|
109
|
+
claude_smart_source_login_path
|
|
110
|
+
claude_smart_prepend_node_bins
|
|
111
|
+
NPM_BIN=$(claude_smart_resolve_npm || true)
|
|
112
|
+
fi
|
|
113
|
+
if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
|
|
114
|
+
reason="npm is not on PATH after installer; dashboard cannot start"
|
|
115
|
+
echo "[claude-smart] dashboard: $reason; skipping" >>"$LOG_FILE"
|
|
116
|
+
claude_smart_write_dashboard_unavailable "$reason"
|
|
117
|
+
emit_ok; exit 0
|
|
118
|
+
fi
|
|
107
119
|
fi
|
|
108
120
|
|
|
109
121
|
# `npm run start` requires a prior `next build`. Do NOT build in the
|
|
@@ -26,6 +26,10 @@ fi
|
|
|
26
26
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
27
27
|
# shellcheck source=_lib.sh
|
|
28
28
|
. "$HERE/_lib.sh"
|
|
29
|
+
if claude_smart_is_internal_invocation_env; then
|
|
30
|
+
echo '{"continue":true,"suppressOutput":true}'
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
29
33
|
# Pick up uv from the user's login-shell PATH (covers ~/.local/bin populated
|
|
30
34
|
# by the astral.sh installer) so a fresh install works before the user
|
|
31
35
|
# restarts their terminal. Matches the pattern used by smart-install.sh.
|
|
@@ -37,11 +41,22 @@ claude_smart_prepend_astral_bins
|
|
|
37
41
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
38
42
|
|
|
39
43
|
FAILURE_MARKER="$HOME/.claude-smart/install-failed"
|
|
44
|
+
STATE_DIR="$HOME/.claude-smart"
|
|
40
45
|
if [ -f "$FAILURE_MARKER" ]; then
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
# Auto-clear the marker when install inputs have changed since the
|
|
47
|
+
# failure was recorded (pyproject.toml / uv.lock / smart-install.sh /
|
|
48
|
+
# python interpreter etc.). Without this the plugin stays muted forever
|
|
49
|
+
# after a transient sync failure even when the user has fixed it.
|
|
50
|
+
stored_fp="$(awk -F= '$1=="fingerprint"{print $2; exit}' "$FAILURE_MARKER" 2>/dev/null || true)"
|
|
51
|
+
current_fp="$(claude_smart_install_fingerprint_hash "$PLUGIN_ROOT" "$HERE" 2>/dev/null || true)"
|
|
52
|
+
if [ -z "$stored_fp" ] || [ "$stored_fp" != "$current_fp" ]; then
|
|
53
|
+
rm -f "$FAILURE_MARKER"
|
|
54
|
+
else
|
|
55
|
+
if [ "$EVENT" = "session-start" ] && command -v python3 >/dev/null 2>&1; then
|
|
56
|
+
python3 - "$FAILURE_MARKER" <<'PY'
|
|
43
57
|
import json, pathlib, sys
|
|
44
|
-
|
|
58
|
+
first = pathlib.Path(sys.argv[1]).read_text().splitlines()
|
|
59
|
+
msg = (first[0].strip() if first else "") or "unknown error"
|
|
45
60
|
print(json.dumps({
|
|
46
61
|
"hookSpecificOutput": {
|
|
47
62
|
"hookEventName": "SessionStart",
|
|
@@ -54,16 +69,42 @@ print(json.dumps({
|
|
|
54
69
|
}
|
|
55
70
|
}))
|
|
56
71
|
PY
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
else
|
|
73
|
+
echo '{"continue":true,"suppressOutput":true}'
|
|
74
|
+
fi
|
|
75
|
+
exit 0
|
|
59
76
|
fi
|
|
60
|
-
exit 0
|
|
61
77
|
fi
|
|
62
78
|
|
|
63
79
|
if ! command -v uv >/dev/null 2>&1; then
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
# Self-heal from skipped Setup/SessionStart bootstrap. SessionStart can
|
|
81
|
+
# afford to wait because it has the install budget; prompt/tool hooks start
|
|
82
|
+
# the same installer detached so normal work is not blocked by first-run
|
|
83
|
+
# dependency setup.
|
|
84
|
+
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" = "1" ]; then
|
|
85
|
+
echo '{"continue":true,"suppressOutput":true}'
|
|
86
|
+
exit 0
|
|
87
|
+
fi
|
|
88
|
+
if [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
89
|
+
mkdir -p "$STATE_DIR"
|
|
90
|
+
if [ "$EVENT" = "session-start" ]; then
|
|
91
|
+
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >&2
|
|
92
|
+
claude_smart_prepend_astral_bins
|
|
93
|
+
claude_smart_prepend_node_bins
|
|
94
|
+
if command -v uv >/dev/null 2>&1; then
|
|
95
|
+
bash "$HERE/backend-service.sh" start >/dev/null 2>&1 || true
|
|
96
|
+
bash "$HERE/dashboard-service.sh" start >/dev/null 2>&1 || true
|
|
97
|
+
fi
|
|
98
|
+
else
|
|
99
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
100
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
101
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
105
|
+
echo '{"continue":true,"suppressOutput":true}'
|
|
106
|
+
exit 0
|
|
107
|
+
fi
|
|
67
108
|
fi
|
|
68
109
|
|
|
69
110
|
# Stdin is the hook payload JSON — stream it through to the Python CLI.
|