claude-smart 0.2.31 → 0.2.33
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 +222 -20
- 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 +4 -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 +21 -1
- package/plugin/dashboard/package-lock.json +70 -95
- package/plugin/dashboard/package.json +5 -2
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +61 -0
- package/plugin/scripts/backend-service.sh +13 -2
- package/plugin/scripts/cli.sh +1 -0
- package/plugin/scripts/codex-hook.js +101 -3
- package/plugin/scripts/dashboard-service.sh +95 -19
- package/plugin/scripts/hook_entry.sh +13 -7
- package/plugin/scripts/smart-install.sh +18 -13
- package/plugin/src/claude_smart/cli.py +138 -24
- 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 +103 -0
- package/plugin/src/claude_smart/events/stop.py +32 -6
- package/plugin/src/claude_smart/ids.py +32 -0
- package/plugin/src/claude_smart/internal_call.py +30 -0
- package/plugin/src/claude_smart/publish.py +8 -3
- package/plugin/src/claude_smart/reflexio_adapter.py +106 -29
- package/plugin/uv.lock +1 -1
package/plugin/scripts/_lib.sh
CHANGED
|
@@ -32,6 +32,59 @@ claude_smart_prepend_node_bins() {
|
|
|
32
32
|
export PATH="$_CS_NODE_ROOT/bin:$_CS_NODE_ROOT:$PATH"
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
claude_smart_env_unquote() {
|
|
36
|
+
local value first last
|
|
37
|
+
value="$1"
|
|
38
|
+
first="${value%"${value#?}"}"
|
|
39
|
+
last="${value#"${value%?}"}"
|
|
40
|
+
if { [ "$first" = '"' ] && [ "$last" = '"' ]; } || { [ "$first" = "'" ] && [ "$last" = "'" ]; }; then
|
|
41
|
+
value="${value#?}"
|
|
42
|
+
value="${value%?}"
|
|
43
|
+
fi
|
|
44
|
+
printf '%s\n' "$value"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
claude_smart_source_reflexio_env() {
|
|
48
|
+
local env_file line key value
|
|
49
|
+
env_file="$HOME/.reflexio/.env"
|
|
50
|
+
[ -f "$env_file" ] || return 0
|
|
51
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
52
|
+
line="${line#"${line%%[![:space:]]*}"}"
|
|
53
|
+
line="${line%"${line##*[![:space:]]}"}"
|
|
54
|
+
[ -n "$line" ] || continue
|
|
55
|
+
case "$line" in
|
|
56
|
+
\#*) continue ;;
|
|
57
|
+
export\ *) line="${line#export }" ;;
|
|
58
|
+
esac
|
|
59
|
+
key="${line%%=*}"
|
|
60
|
+
[ "$key" != "$line" ] || continue
|
|
61
|
+
value="${line#*=}"
|
|
62
|
+
key="${key%"${key##*[![:space:]]}"}"
|
|
63
|
+
value="${value#"${value%%[![:space:]]*}"}"
|
|
64
|
+
value="${value%"${value##*[![:space:]]}"}"
|
|
65
|
+
case "$key" in
|
|
66
|
+
REFLEXIO_URL|REFLEXIO_API_KEY|REFLEXIO_USER_ID|CLAUDE_SMART_USE_LOCAL_CLI|CLAUDE_SMART_USE_LOCAL_EMBEDDING|CLAUDE_SMART_BACKEND_AUTOSTART|CLAUDE_SMART_DASHBOARD_AUTOSTART|CLAUDE_SMART_CLI_PATH|CLAUDE_SMART_CLI_TIMEOUT|CLAUDE_SMART_STATE_DIR|CLAUDE_SMART_ENABLE_OPTIMIZER)
|
|
67
|
+
if [ -z "$(eval "printf '%s' \"\${$key:-}\"")" ]; then
|
|
68
|
+
value="$(claude_smart_env_unquote "$value")"
|
|
69
|
+
export "$key=$value"
|
|
70
|
+
fi
|
|
71
|
+
;;
|
|
72
|
+
esac
|
|
73
|
+
done < "$env_file"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
claude_smart_reflexio_url_is_remote() {
|
|
77
|
+
local url
|
|
78
|
+
url="${REFLEXIO_URL:-}"
|
|
79
|
+
[ -n "$url" ] || return 1
|
|
80
|
+
case "$url" in
|
|
81
|
+
http://localhost|http://localhost/|http://localhost:*|http://127.0.0.1|http://127.0.0.1/|http://127.0.0.1:*|http://0.0.0.0|http://0.0.0.0/|http://0.0.0.0:*|http://\[::1\]|http://\[::1\]/|http://\[::1\]:*)
|
|
82
|
+
return 1
|
|
83
|
+
;;
|
|
84
|
+
esac
|
|
85
|
+
return 0
|
|
86
|
+
}
|
|
87
|
+
|
|
35
88
|
claude_smart_is_internal_invocation_env() {
|
|
36
89
|
if [ "${CLAUDE_SMART_INTERNAL:-}" = "1" ]; then
|
|
37
90
|
return 0
|
|
@@ -42,6 +95,14 @@ claude_smart_is_internal_invocation_env() {
|
|
|
42
95
|
return 1
|
|
43
96
|
}
|
|
44
97
|
|
|
98
|
+
claude_smart_emit_continue() {
|
|
99
|
+
if [ "${CLAUDE_SMART_HOST:-claude-code}" = "codex" ]; then
|
|
100
|
+
echo '{"continue":true}'
|
|
101
|
+
else
|
|
102
|
+
echo '{"continue":true,"suppressOutput":true}'
|
|
103
|
+
fi
|
|
104
|
+
}
|
|
105
|
+
|
|
45
106
|
claude_smart_dashboard_unavailable_marker() {
|
|
46
107
|
printf '%s\n' "$HOME/.claude-smart/dashboard-unavailable"
|
|
47
108
|
}
|
|
@@ -22,6 +22,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
|
22
22
|
. "$HERE/_lib.sh"
|
|
23
23
|
claude_smart_source_login_path
|
|
24
24
|
claude_smart_prepend_astral_bins
|
|
25
|
+
claude_smart_source_reflexio_env
|
|
25
26
|
|
|
26
27
|
CMD="${1:-start}"
|
|
27
28
|
PORT=8071
|
|
@@ -67,7 +68,7 @@ LOG_MAX_BYTES="$(claude_smart_log_max_bytes)"
|
|
|
67
68
|
mkdir -p "$STATE_DIR"
|
|
68
69
|
claude_smart_trim_log_file "$LOG_FILE" "$LOG_MAX_BYTES"
|
|
69
70
|
|
|
70
|
-
emit_ok() {
|
|
71
|
+
emit_ok() { claude_smart_emit_continue; }
|
|
71
72
|
|
|
72
73
|
emit_start_failure() {
|
|
73
74
|
reason="$1"
|
|
@@ -182,6 +183,10 @@ case "$CMD" in
|
|
|
182
183
|
if claude_smart_is_internal_invocation_env; then
|
|
183
184
|
emit_ok; exit 0
|
|
184
185
|
fi
|
|
186
|
+
if claude_smart_reflexio_url_is_remote; then
|
|
187
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: remote REFLEXIO_URL configured; skipping local backend start"
|
|
188
|
+
emit_ok; exit 0
|
|
189
|
+
fi
|
|
185
190
|
# Opt-out: users who don't want the backend managed by the hook can
|
|
186
191
|
# set CLAUDE_SMART_BACKEND_AUTOSTART=0.
|
|
187
192
|
if [ "${CLAUDE_SMART_BACKEND_AUTOSTART:-1}" = "0" ]; then
|
|
@@ -269,7 +274,13 @@ case "$CMD" in
|
|
|
269
274
|
emit_ok
|
|
270
275
|
;;
|
|
271
276
|
status)
|
|
272
|
-
if
|
|
277
|
+
if claude_smart_reflexio_url_is_remote; then
|
|
278
|
+
echo "remote configured at $REFLEXIO_URL"
|
|
279
|
+
elif is_our_backend_running; then
|
|
280
|
+
echo "running on http://localhost:$PORT"
|
|
281
|
+
else
|
|
282
|
+
echo "not running"
|
|
283
|
+
fi
|
|
273
284
|
;;
|
|
274
285
|
*)
|
|
275
286
|
emit_ok
|
package/plugin/scripts/cli.sh
CHANGED
|
@@ -16,7 +16,46 @@ const DASHBOARD_PORT = 3001;
|
|
|
16
16
|
const LOG_MAX_BYTES = 10000000;
|
|
17
17
|
|
|
18
18
|
function emitOk() {
|
|
19
|
-
process.stdout.write('{"continue":true
|
|
19
|
+
process.stdout.write('{"continue":true}\n');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function emitHookOk() {
|
|
23
|
+
process.stdout.write('{"continue":true}\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function emitNormalizedHookOutput(stdout) {
|
|
27
|
+
const lines = String(stdout || "")
|
|
28
|
+
.split(/\r?\n/)
|
|
29
|
+
.map((line) => line.trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
if (lines.length === 0) {
|
|
32
|
+
emitHookOk();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const merged = {};
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(line);
|
|
41
|
+
} catch {
|
|
42
|
+
appendLog("backend.log", `[claude-smart] codex hook emitted non-JSON stdout: ${line.slice(0, 500)}`);
|
|
43
|
+
emitHookOk();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
47
|
+
appendLog("backend.log", `[claude-smart] codex hook emitted non-object JSON stdout: ${line.slice(0, 500)}`);
|
|
48
|
+
emitHookOk();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
Object.assign(merged, parsed);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(merged, "continue")) {
|
|
55
|
+
merged.continue = true;
|
|
56
|
+
}
|
|
57
|
+
delete merged.suppressOutput;
|
|
58
|
+
process.stdout.write(`${JSON.stringify(merged)}\n`);
|
|
20
59
|
}
|
|
21
60
|
|
|
22
61
|
function ensureDir(dir) {
|
|
@@ -121,6 +160,57 @@ function writeBackendUrl(port) {
|
|
|
121
160
|
fs.writeFileSync(backendUrlFile(), `http://localhost:${port}/\n`);
|
|
122
161
|
}
|
|
123
162
|
|
|
163
|
+
function unquoteEnvValue(value) {
|
|
164
|
+
const trimmed = String(value || "").trim();
|
|
165
|
+
if (trimmed.length >= 2) {
|
|
166
|
+
const first = trimmed[0];
|
|
167
|
+
const last = trimmed[trimmed.length - 1];
|
|
168
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
169
|
+
return trimmed.slice(1, -1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return trimmed;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function loadReflexioEnv() {
|
|
176
|
+
const file = path.join(REFLEXIO_DIR, ".env");
|
|
177
|
+
let text;
|
|
178
|
+
try {
|
|
179
|
+
text = fs.readFileSync(file, "utf8");
|
|
180
|
+
} catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const allowed = new Set([
|
|
184
|
+
"REFLEXIO_URL",
|
|
185
|
+
"REFLEXIO_API_KEY",
|
|
186
|
+
"REFLEXIO_USER_ID",
|
|
187
|
+
"CLAUDE_SMART_USE_LOCAL_CLI",
|
|
188
|
+
"CLAUDE_SMART_USE_LOCAL_EMBEDDING",
|
|
189
|
+
"CLAUDE_SMART_BACKEND_AUTOSTART",
|
|
190
|
+
"CLAUDE_SMART_DASHBOARD_AUTOSTART",
|
|
191
|
+
"CLAUDE_SMART_CLI_PATH",
|
|
192
|
+
"CLAUDE_SMART_CLI_TIMEOUT",
|
|
193
|
+
"CLAUDE_SMART_STATE_DIR",
|
|
194
|
+
"CLAUDE_SMART_ENABLE_OPTIMIZER",
|
|
195
|
+
]);
|
|
196
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
197
|
+
let line = rawLine.trim();
|
|
198
|
+
if (!line || line.startsWith("#")) continue;
|
|
199
|
+
if (line.startsWith("export ")) line = line.slice("export ".length).trimStart();
|
|
200
|
+
const eq = line.indexOf("=");
|
|
201
|
+
if (eq < 0) continue;
|
|
202
|
+
const key = line.slice(0, eq).trim();
|
|
203
|
+
if (!allowed.has(key) || process.env[key]) continue;
|
|
204
|
+
process.env[key] = unquoteEnvValue(line.slice(eq + 1));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function reflexioUrlIsRemote() {
|
|
209
|
+
const url = process.env.REFLEXIO_URL || "";
|
|
210
|
+
if (!url) return false;
|
|
211
|
+
return !/^http:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?::\d+)?\/?$/i.test(url);
|
|
212
|
+
}
|
|
213
|
+
|
|
124
214
|
function codexCompatPath(root) {
|
|
125
215
|
const filename = process.platform === "win32"
|
|
126
216
|
? "codex-claude-compat.cmd"
|
|
@@ -294,6 +384,11 @@ async function startBackend(root) {
|
|
|
294
384
|
emitOk();
|
|
295
385
|
return;
|
|
296
386
|
}
|
|
387
|
+
if (reflexioUrlIsRemote()) {
|
|
388
|
+
appendLog("backend.log", "[claude-smart] backend: remote REFLEXIO_URL configured; skipping local backend start");
|
|
389
|
+
emitOk();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
297
392
|
const pidFile = path.join(STATE_DIR, "backend.pid");
|
|
298
393
|
for (const port of [DEFAULT_BACKEND_PORT, FALLBACK_BACKEND_PORT]) {
|
|
299
394
|
if (pidAlive(readPid(pidFile)) && await healthOk(port, "/health")) {
|
|
@@ -419,7 +514,7 @@ function runHook(root, event) {
|
|
|
419
514
|
}
|
|
420
515
|
if (!uv) {
|
|
421
516
|
appendLog("backend.log", "[claude-smart] hook: uv not on PATH after installer; skipping");
|
|
422
|
-
|
|
517
|
+
emitHookOk();
|
|
423
518
|
return 0;
|
|
424
519
|
}
|
|
425
520
|
}
|
|
@@ -434,17 +529,20 @@ function runHook(root, event) {
|
|
|
434
529
|
REFLEXIO_URL: readBackendUrl(),
|
|
435
530
|
CLAUDE_SMART_HOST: "codex",
|
|
436
531
|
CLAUDE_SMART_CLI_PATH: process.env.CLAUDE_SMART_CLI_PATH || codexCompatPath(root),
|
|
532
|
+
CLAUDE_SMART_CITATION_LINK_STYLE: process.env.CLAUDE_SMART_CITATION_LINK_STYLE || "osc8",
|
|
437
533
|
},
|
|
438
534
|
input,
|
|
439
|
-
stdio: ["pipe", "
|
|
535
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
440
536
|
windowsHide: true,
|
|
441
537
|
},
|
|
442
538
|
);
|
|
539
|
+
emitNormalizedHookOutput(result.stdout);
|
|
443
540
|
return typeof result.status === "number" ? result.status : 1;
|
|
444
541
|
}
|
|
445
542
|
|
|
446
543
|
async function main() {
|
|
447
544
|
prependRuntimePath();
|
|
545
|
+
loadReflexioEnv();
|
|
448
546
|
const root = pluginRoot();
|
|
449
547
|
process.env.PLUGIN_ROOT = root;
|
|
450
548
|
process.env.CLAUDE_PLUGIN_ROOT = root;
|
|
@@ -23,6 +23,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
|
23
23
|
. "$HERE/_lib.sh"
|
|
24
24
|
claude_smart_source_login_path
|
|
25
25
|
claude_smart_prepend_node_bins
|
|
26
|
+
claude_smart_source_reflexio_env
|
|
26
27
|
|
|
27
28
|
CMD="${1:-start}"
|
|
28
29
|
PORT=3001
|
|
@@ -36,7 +37,7 @@ PID_FILE="$STATE_DIR/dashboard.pid"
|
|
|
36
37
|
LOG_FILE="$STATE_DIR/dashboard.log"
|
|
37
38
|
mkdir -p "$STATE_DIR"
|
|
38
39
|
|
|
39
|
-
emit_ok() {
|
|
40
|
+
emit_ok() { claude_smart_emit_continue; }
|
|
40
41
|
|
|
41
42
|
# Tree-kill the recorded process. Delegates to claude_smart_kill_tree
|
|
42
43
|
# (POSIX: signal the process group; Windows: taskkill /T /F /PID).
|
|
@@ -45,15 +46,96 @@ kill_group() {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
# True if the marker header served by app/api/health is present on the
|
|
48
|
-
# port. Requires curl — absence is reported as false.
|
|
49
|
+
# port. Requires curl — absence is reported as false. This deliberately
|
|
50
|
+
# accepts any claude-smart dashboard, including stale cache versions, so
|
|
51
|
+
# stop/uninstall can safely reap them without touching foreign listeners.
|
|
49
52
|
marker_responds() {
|
|
50
53
|
command -v curl >/dev/null 2>&1 || return 1
|
|
51
54
|
curl -sfI "http://127.0.0.1:$PORT/api/health" 2>/dev/null \
|
|
52
55
|
| grep -qi '^x-claude-smart-dashboard:'
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
dashboard_health_headers() {
|
|
59
|
+
command -v curl >/dev/null 2>&1 || return 1
|
|
60
|
+
curl -sfI --connect-timeout 2 --max-time 5 "http://127.0.0.1:$PORT/api/health" 2>/dev/null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
header_value_from() {
|
|
64
|
+
header_name="$1"
|
|
65
|
+
awk -v wanted="$header_name" '
|
|
66
|
+
BEGIN { wanted = tolower(wanted) ":" }
|
|
67
|
+
{
|
|
68
|
+
line = $0
|
|
69
|
+
sub(/\r$/, "", line)
|
|
70
|
+
lower = tolower(line)
|
|
71
|
+
if (index(lower, wanted) == 1) {
|
|
72
|
+
sub(/^[^:]*:[[:space:]]*/, "", line)
|
|
73
|
+
print line
|
|
74
|
+
exit
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
canonical_dir() {
|
|
81
|
+
dir="$1"
|
|
82
|
+
(cd "$dir" 2>/dev/null && pwd -P) || printf '%s\n' "$dir"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
normalize_identity_path() {
|
|
86
|
+
path="$1"
|
|
87
|
+
if claude_smart_is_windows; then
|
|
88
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
89
|
+
path="$(cygpath -u "$path" 2>/dev/null || printf '%s\n' "$path")"
|
|
90
|
+
else
|
|
91
|
+
path="$(
|
|
92
|
+
printf '%s\n' "$path" | awk '{
|
|
93
|
+
gsub(/\\/, "/")
|
|
94
|
+
if ($0 ~ /^[A-Za-z]:/) {
|
|
95
|
+
drive = tolower(substr($0, 1, 1))
|
|
96
|
+
sub(/^[A-Za-z]:/, "/" drive)
|
|
97
|
+
}
|
|
98
|
+
print
|
|
99
|
+
}'
|
|
100
|
+
)"
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
while [ "${path%/}" != "$path" ] && [ "$path" != "/" ]; do
|
|
104
|
+
path="${path%/}"
|
|
105
|
+
done
|
|
106
|
+
printf '%s\n' "$path"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# True only if *this plugin root's* dashboard is on the port. The generic
|
|
110
|
+
# x-claude-smart-dashboard marker is intentionally not enough: after an
|
|
111
|
+
# install/update, an older cache can keep serving port 3001 until restarted.
|
|
112
|
+
dashboard_matches_current_root() {
|
|
113
|
+
expected_root="$(normalize_identity_path "$(canonical_dir "$PLUGIN_ROOT")")"
|
|
114
|
+
headers="$(dashboard_health_headers)" || return 1
|
|
115
|
+
printf '%s\n' "$headers" | grep -qi '^x-claude-smart-dashboard:' || return 1
|
|
116
|
+
actual_root="$(printf '%s\n' "$headers" | header_value_from "x-claude-smart-plugin-root")"
|
|
117
|
+
[ -n "$actual_root" ] || return 1
|
|
118
|
+
actual_root="$(normalize_identity_path "$actual_root")"
|
|
119
|
+
[ "$actual_root" = "$expected_root" ]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Kill a claude-smart dashboard listener currently holding the port. Gated by
|
|
123
|
+
# marker_responds so a foreign app on 3001 is never killed.
|
|
124
|
+
stop_dashboard_listener() {
|
|
125
|
+
marker_responds || return 0
|
|
126
|
+
command -v lsof >/dev/null 2>&1 || return 0
|
|
127
|
+
port_pid=$(lsof -t -i ":$PORT" -sTCP:LISTEN 2>/dev/null | head -n1)
|
|
128
|
+
[ -n "$port_pid" ] || return 0
|
|
129
|
+
kill -TERM "$port_pid" 2>/dev/null || true
|
|
130
|
+
for _ in 1 2 3 4 5; do
|
|
131
|
+
kill -0 "$port_pid" 2>/dev/null || return 0
|
|
132
|
+
sleep 0.2
|
|
133
|
+
done
|
|
134
|
+
kill -KILL "$port_pid" 2>/dev/null || true
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# True only if *our* dashboard is on the port. Uses plugin-root identity so a
|
|
138
|
+
# stale claude-smart listener doesn't cause us to silently skip starting.
|
|
57
139
|
is_our_dashboard_running() {
|
|
58
140
|
if [ -f "$PID_FILE" ]; then
|
|
59
141
|
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
@@ -62,7 +144,7 @@ is_our_dashboard_running() {
|
|
|
62
144
|
# don't claim "running" when the server crashed but the group leader
|
|
63
145
|
# lingered.
|
|
64
146
|
if command -v curl >/dev/null 2>&1; then
|
|
65
|
-
|
|
147
|
+
dashboard_matches_current_root && return 0
|
|
66
148
|
else
|
|
67
149
|
# No curl — fall back to PID liveness alone.
|
|
68
150
|
return 0
|
|
@@ -71,7 +153,7 @@ is_our_dashboard_running() {
|
|
|
71
153
|
fi
|
|
72
154
|
# No PID or dead PID — probe the port for our marker (recovers after a
|
|
73
155
|
# stale PID file from a crash).
|
|
74
|
-
|
|
156
|
+
dashboard_matches_current_root && return 0
|
|
75
157
|
return 1
|
|
76
158
|
}
|
|
77
159
|
|
|
@@ -97,6 +179,10 @@ case "$CMD" in
|
|
|
97
179
|
fi
|
|
98
180
|
if [ ! -d "$DASHBOARD_DIR" ]; then emit_ok; exit 0; fi
|
|
99
181
|
if is_our_dashboard_running; then claude_smart_clear_dashboard_unavailable; emit_ok; exit 0; fi
|
|
182
|
+
if marker_responds; then
|
|
183
|
+
echo "[claude-smart] dashboard: stale claude-smart dashboard on port $PORT; restarting from $PLUGIN_ROOT" >>"$LOG_FILE"
|
|
184
|
+
stop_dashboard_listener
|
|
185
|
+
fi
|
|
100
186
|
if port_occupied; then
|
|
101
187
|
echo "[claude-smart] dashboard: port $PORT held by another process; skipping" >>"$LOG_FILE"
|
|
102
188
|
emit_ok; exit 0
|
|
@@ -152,7 +238,7 @@ case "$CMD" in
|
|
|
152
238
|
echo "$dash_pid" > "$PID_FILE"
|
|
153
239
|
dashboard_ready=0
|
|
154
240
|
for _ in 1 2 3 4 5; do
|
|
155
|
-
if
|
|
241
|
+
if dashboard_matches_current_root; then
|
|
156
242
|
dashboard_ready=1
|
|
157
243
|
claude_smart_clear_dashboard_unavailable
|
|
158
244
|
break
|
|
@@ -169,21 +255,11 @@ case "$CMD" in
|
|
|
169
255
|
kill_group "$(cat "$PID_FILE" 2>/dev/null)"
|
|
170
256
|
rm -f "$PID_FILE"
|
|
171
257
|
fi
|
|
172
|
-
# Fallback: if
|
|
258
|
+
# Fallback: if a claude-smart dashboard is still responding on the port (e.g.,
|
|
173
259
|
# was started outside this script, or the PGID kill missed because
|
|
174
260
|
# the process wasn't the group leader) kill whoever owns the port.
|
|
175
261
|
# Gated on the marker header so we never touch a foreign listener.
|
|
176
|
-
|
|
177
|
-
port_pid=$(lsof -t -i ":$PORT" -sTCP:LISTEN 2>/dev/null | head -n1)
|
|
178
|
-
if [ -n "$port_pid" ]; then
|
|
179
|
-
kill -TERM "$port_pid" 2>/dev/null || true
|
|
180
|
-
for _ in 1 2 3 4 5; do
|
|
181
|
-
kill -0 "$port_pid" 2>/dev/null || break
|
|
182
|
-
sleep 0.2
|
|
183
|
-
done
|
|
184
|
-
kill -KILL "$port_pid" 2>/dev/null || true
|
|
185
|
-
fi
|
|
186
|
-
fi
|
|
262
|
+
stop_dashboard_listener
|
|
187
263
|
emit_ok
|
|
188
264
|
;;
|
|
189
265
|
session-end)
|
|
@@ -18,16 +18,21 @@ case "$EVENT" in
|
|
|
18
18
|
EVENT="${2:-}"
|
|
19
19
|
;;
|
|
20
20
|
esac
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
export CLAUDE_SMART_HOST="$HOST"
|
|
22
|
+
if [ "$HOST" = "codex" ] && [ -z "${CLAUDE_SMART_CITATION_LINK_STYLE:-}" ]; then
|
|
23
|
+
export CLAUDE_SMART_CITATION_LINK_STYLE="osc8"
|
|
24
24
|
fi
|
|
25
25
|
|
|
26
26
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
27
27
|
# shellcheck source=_lib.sh
|
|
28
28
|
. "$HERE/_lib.sh"
|
|
29
|
+
if [ -z "$EVENT" ]; then
|
|
30
|
+
claude_smart_emit_continue
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
29
34
|
if claude_smart_is_internal_invocation_env; then
|
|
30
|
-
|
|
35
|
+
claude_smart_emit_continue
|
|
31
36
|
exit 0
|
|
32
37
|
fi
|
|
33
38
|
# Pick up uv from the user's login-shell PATH (covers ~/.local/bin populated
|
|
@@ -37,6 +42,7 @@ claude_smart_source_login_path
|
|
|
37
42
|
# Explicit fallback for the astral.sh installer's default paths, in case
|
|
38
43
|
# the user's login-shell rc hasn't yet been re-sourced to pick them up.
|
|
39
44
|
claude_smart_prepend_astral_bins
|
|
45
|
+
claude_smart_source_reflexio_env
|
|
40
46
|
|
|
41
47
|
PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
|
|
42
48
|
|
|
@@ -70,7 +76,7 @@ print(json.dumps({
|
|
|
70
76
|
}))
|
|
71
77
|
PY
|
|
72
78
|
else
|
|
73
|
-
|
|
79
|
+
claude_smart_emit_continue
|
|
74
80
|
fi
|
|
75
81
|
exit 0
|
|
76
82
|
fi
|
|
@@ -82,7 +88,7 @@ if ! command -v uv >/dev/null 2>&1; then
|
|
|
82
88
|
# the same installer detached so normal work is not blocked by first-run
|
|
83
89
|
# dependency setup.
|
|
84
90
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" = "1" ]; then
|
|
85
|
-
|
|
91
|
+
claude_smart_emit_continue
|
|
86
92
|
exit 0
|
|
87
93
|
fi
|
|
88
94
|
if [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
@@ -102,7 +108,7 @@ if ! command -v uv >/dev/null 2>&1; then
|
|
|
102
108
|
fi
|
|
103
109
|
fi
|
|
104
110
|
if ! command -v uv >/dev/null 2>&1; then
|
|
105
|
-
|
|
111
|
+
claude_smart_emit_continue
|
|
106
112
|
exit 0
|
|
107
113
|
fi
|
|
108
114
|
fi
|
|
@@ -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
|
|
@@ -91,7 +92,7 @@ write_failure() {
|
|
|
91
92
|
} > "$FAILURE_MARKER"
|
|
92
93
|
rm -f "$SUCCESS_MARKER"
|
|
93
94
|
echo "[claude-smart] install failed: $reason" >&2
|
|
94
|
-
|
|
95
|
+
claude_smart_emit_continue
|
|
95
96
|
exit 0
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -105,8 +106,10 @@ install_complete() {
|
|
|
105
106
|
command -v uv >/dev/null 2>&1 || return 1
|
|
106
107
|
[ -d "$PLUGIN_ROOT/.venv" ] || return 1
|
|
107
108
|
[ -f "$HOME/.reflexio/.env" ] || return 1
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
110
113
|
if [ -d "$PLUGIN_ROOT/dashboard" ]; then
|
|
111
114
|
[ -d "$PLUGIN_ROOT/dashboard/.next" ] || [ -f "$MARKER_DIR/dashboard-build.pid" ] || [ -f "$(claude_smart_dashboard_unavailable_marker)" ] || return 1
|
|
112
115
|
fi
|
|
@@ -360,7 +363,7 @@ fi
|
|
|
360
363
|
preflight_supported_runtime_platform
|
|
361
364
|
|
|
362
365
|
if install_complete; then
|
|
363
|
-
|
|
366
|
+
claude_smart_emit_continue
|
|
364
367
|
exit 0
|
|
365
368
|
fi
|
|
366
369
|
|
|
@@ -431,13 +434,15 @@ fi
|
|
|
431
434
|
REFLEXIO_ENV="$HOME/.reflexio/.env"
|
|
432
435
|
mkdir -p "$(dirname "$REFLEXIO_ENV")"
|
|
433
436
|
touch "$REFLEXIO_ENV"
|
|
434
|
-
if
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
|
441
446
|
fi
|
|
442
447
|
# Migrate stale REFLEXIO_URL from reflexio's library default (8081) to the
|
|
443
448
|
# plugin backend port (8071). Matches the quoted and unquoted forms but
|
|
@@ -511,4 +516,4 @@ fi
|
|
|
511
516
|
|
|
512
517
|
write_success_marker
|
|
513
518
|
echo "[claude-smart] install complete. Backend and dashboard auto-start on session start." >&2
|
|
514
|
-
|
|
519
|
+
claude_smart_emit_continue
|