claude-smart 0.2.29 → 0.2.30
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 +3 -3
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/backend-service.sh +10 -2
- package/plugin/scripts/codex-hook.js +72 -9
- package/plugin/scripts/dashboard-service.sh +13 -4
- package/plugin/scripts/hook_entry.sh +29 -3
- package/plugin/scripts/smart-install.sh +48 -8
- 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.30-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
|
}
|
package/plugin/pyproject.toml
CHANGED
|
@@ -186,8 +186,16 @@ case "$CMD" in
|
|
|
186
186
|
emit_ok; exit 0
|
|
187
187
|
fi
|
|
188
188
|
if ! command -v uv >/dev/null 2>&1; then
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
190
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; running installer"
|
|
191
|
+
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >>"$STATE_DIR/install.log" 2>&1 || true
|
|
192
|
+
claude_smart_source_login_path
|
|
193
|
+
claude_smart_prepend_astral_bins
|
|
194
|
+
fi
|
|
195
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
196
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH after installer; skipping"
|
|
197
|
+
emit_ok; exit 0
|
|
198
|
+
fi
|
|
191
199
|
fi
|
|
192
200
|
cd "$PLUGIN_ROOT"
|
|
193
201
|
|
|
@@ -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(
|
|
@@ -100,10 +100,19 @@ case "$CMD" in
|
|
|
100
100
|
fi
|
|
101
101
|
NPM_BIN=$(claude_smart_resolve_npm || true)
|
|
102
102
|
if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
104
|
+
echo "[claude-smart] dashboard: npm is not on PATH; running installer" >>"$LOG_FILE"
|
|
105
|
+
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >>"$STATE_DIR/install.log" 2>&1 || true
|
|
106
|
+
claude_smart_source_login_path
|
|
107
|
+
claude_smart_prepend_node_bins
|
|
108
|
+
NPM_BIN=$(claude_smart_resolve_npm || true)
|
|
109
|
+
fi
|
|
110
|
+
if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
|
|
111
|
+
reason="npm is not on PATH after installer; dashboard cannot start"
|
|
112
|
+
echo "[claude-smart] dashboard: $reason; skipping" >>"$LOG_FILE"
|
|
113
|
+
claude_smart_write_dashboard_unavailable "$reason"
|
|
114
|
+
emit_ok; exit 0
|
|
115
|
+
fi
|
|
107
116
|
fi
|
|
108
117
|
|
|
109
118
|
# `npm run start` requires a prior `next build`. Do NOT build in the
|
|
@@ -37,6 +37,7 @@ claude_smart_prepend_astral_bins
|
|
|
37
37
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
38
38
|
|
|
39
39
|
FAILURE_MARKER="$HOME/.claude-smart/install-failed"
|
|
40
|
+
STATE_DIR="$HOME/.claude-smart"
|
|
40
41
|
if [ -f "$FAILURE_MARKER" ]; then
|
|
41
42
|
if [ "$EVENT" = "session-start" ] && command -v python3 >/dev/null 2>&1; then
|
|
42
43
|
python3 - "$FAILURE_MARKER" <<'PY'
|
|
@@ -61,9 +62,34 @@ PY
|
|
|
61
62
|
fi
|
|
62
63
|
|
|
63
64
|
if ! command -v uv >/dev/null 2>&1; then
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
# Self-heal from skipped Setup/SessionStart bootstrap. SessionStart can
|
|
66
|
+
# afford to wait because it has the install budget; prompt/tool hooks start
|
|
67
|
+
# the same installer detached so normal work is not blocked by first-run
|
|
68
|
+
# dependency setup.
|
|
69
|
+
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" = "1" ]; then
|
|
70
|
+
echo '{"continue":true,"suppressOutput":true}'
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
if [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
74
|
+
mkdir -p "$STATE_DIR"
|
|
75
|
+
if [ "$EVENT" = "session-start" ]; then
|
|
76
|
+
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >&2
|
|
77
|
+
claude_smart_prepend_astral_bins
|
|
78
|
+
claude_smart_prepend_node_bins
|
|
79
|
+
if command -v uv >/dev/null 2>&1; then
|
|
80
|
+
bash "$HERE/backend-service.sh" start >/dev/null 2>&1 || true
|
|
81
|
+
bash "$HERE/dashboard-service.sh" start >/dev/null 2>&1 || true
|
|
82
|
+
fi
|
|
83
|
+
else
|
|
84
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
85
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
86
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
90
|
+
echo '{"continue":true,"suppressOutput":true}'
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
67
93
|
fi
|
|
68
94
|
|
|
69
95
|
# Stdin is the hook payload JSON — stream it through to the Python CLI.
|
|
@@ -22,20 +22,60 @@ MARKER_DIR="$HOME/.claude-smart"
|
|
|
22
22
|
FAILURE_MARKER="$MARKER_DIR/install-failed"
|
|
23
23
|
SUCCESS_MARKER="$MARKER_DIR/install-complete"
|
|
24
24
|
INSTALL_LOCK="$MARKER_DIR/install.lock"
|
|
25
|
+
INSTALL_REAP_LOCK="$MARKER_DIR/install.lock.reap"
|
|
25
26
|
mkdir -p "$MARKER_DIR"
|
|
26
27
|
|
|
28
|
+
remove_stale_install_lock() {
|
|
29
|
+
local expected current
|
|
30
|
+
|
|
31
|
+
expected="$1"
|
|
32
|
+
if ! mkdir "$INSTALL_REAP_LOCK" 2>/dev/null; then
|
|
33
|
+
sleep 1
|
|
34
|
+
return 0
|
|
35
|
+
fi
|
|
36
|
+
current="$(cat "$INSTALL_LOCK" 2>/dev/null || true)"
|
|
37
|
+
if [ "$current" = "$expected" ]; then
|
|
38
|
+
rm -f "$INSTALL_LOCK"
|
|
39
|
+
fi
|
|
40
|
+
rmdir "$INSTALL_REAP_LOCK" 2>/dev/null || true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
acquire_install_lock() {
|
|
44
|
+
local lock_pid
|
|
45
|
+
|
|
46
|
+
if command -v flock >/dev/null 2>&1; then
|
|
47
|
+
exec 9>"$INSTALL_LOCK"
|
|
48
|
+
if ! flock 9; then
|
|
49
|
+
echo "[claude-smart] install lock failed; continuing without serialization" >&2
|
|
50
|
+
echo '{"continue":true,"suppressOutput":true}'
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
return 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
while ! ( set -C; printf '%s\n' "$$" > "$INSTALL_LOCK" ) 2>/dev/null; do
|
|
57
|
+
lock_pid="$(cat "$INSTALL_LOCK" 2>/dev/null || true)"
|
|
58
|
+
case "$lock_pid" in
|
|
59
|
+
''|*[!0-9]*)
|
|
60
|
+
remove_stale_install_lock "$lock_pid"
|
|
61
|
+
;;
|
|
62
|
+
*)
|
|
63
|
+
if kill -0 "$lock_pid" 2>/dev/null; then
|
|
64
|
+
sleep 1
|
|
65
|
+
else
|
|
66
|
+
remove_stale_install_lock "$lock_pid"
|
|
67
|
+
fi
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
done
|
|
71
|
+
trap '[ "$(cat "$INSTALL_LOCK" 2>/dev/null || true)" = "$$" ] && rm -f "$INSTALL_LOCK" || true' EXIT
|
|
72
|
+
}
|
|
73
|
+
|
|
27
74
|
# Serialize concurrent installer runs (SessionStart hook + slash-command
|
|
28
75
|
# self-heal can both invoke this script). Wait for the active installer
|
|
29
76
|
# rather than returning early, otherwise callers can re-check uv before
|
|
30
77
|
# the first install has finished and report a false missing-dependency error.
|
|
31
|
-
|
|
32
|
-
exec 9>"$INSTALL_LOCK"
|
|
33
|
-
if ! flock 9; then
|
|
34
|
-
echo "[claude-smart] install lock failed; continuing without serialization" >&2
|
|
35
|
-
echo '{"continue":true,"suppressOutput":true}'
|
|
36
|
-
exit 0
|
|
37
|
-
fi
|
|
38
|
-
fi
|
|
78
|
+
acquire_install_lock
|
|
39
79
|
|
|
40
80
|
rm -f "$FAILURE_MARKER"
|
|
41
81
|
|