claude-smart 0.2.39 → 0.2.41
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 +1 -1
- package/bin/claude-smart.js +92 -5
- 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 +0 -5
- package/plugin/hooks/hooks.json +0 -10
- package/plugin/pyproject.toml +5 -1
- package/plugin/scripts/backend-service.sh +5 -5
- package/plugin/scripts/codex-hook.js +8 -39
- package/plugin/scripts/dashboard-service.sh +5 -6
- package/plugin/scripts/hook_entry.sh +17 -31
- package/plugin/scripts/smart-install.sh +74 -1
- package/plugin/src/claude_smart/cli.py +8 -2
- package/plugin/src/claude_smart/context_format.py +50 -5
- package/plugin/src/claude_smart/cs_cite.py +12 -1
- package/plugin/src/claude_smart/env_config.py +74 -0
- package/plugin/uv.lock +15 -1
- package/scripts/setup-claude-smart.sh +31 -3
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
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.41-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">
|
package/bin/claude-smart.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
const { execSync, spawn, spawnSync } = require("child_process");
|
|
17
17
|
const crypto = require("crypto");
|
|
18
18
|
const {
|
|
19
|
+
chmodSync,
|
|
19
20
|
cpSync,
|
|
20
21
|
existsSync,
|
|
21
22
|
lstatSync,
|
|
@@ -41,6 +42,8 @@ const REFLEXIO_ENV_PATH = join(homedir(), ".reflexio", ".env");
|
|
|
41
42
|
const MANAGED_REFLEXIO_URL = "https://www.reflexio.ai/";
|
|
42
43
|
const MANAGED_SETUP_ENV = "CLAUDE_SMART_MANAGED_SETUP";
|
|
43
44
|
const CLAUDE_SMART_READ_ONLY_ENV = "CLAUDE_SMART_READ_ONLY";
|
|
45
|
+
const CLAUDE_SMART_USE_LOCAL_CLI_ENV = "CLAUDE_SMART_USE_LOCAL_CLI";
|
|
46
|
+
const CLAUDE_SMART_USE_LOCAL_EMBEDDING_ENV = "CLAUDE_SMART_USE_LOCAL_EMBEDDING";
|
|
44
47
|
const REFLEXIO_USER_ID_ENV = "REFLEXIO_USER_ID";
|
|
45
48
|
const REFLEXIO_DIR = join(homedir(), ".reflexio");
|
|
46
49
|
const CLAUDE_SMART_STATE_DIR = join(homedir(), ".claude-smart");
|
|
@@ -90,6 +93,24 @@ const COPYTREE_IGNORE_NAMES = new Set([
|
|
|
90
93
|
"node_modules",
|
|
91
94
|
".next",
|
|
92
95
|
]);
|
|
96
|
+
const LOCAL_DEFAULT_ENV_ENTRIES = [
|
|
97
|
+
[
|
|
98
|
+
"# Route reflexio generation through the local Claude Code CLI",
|
|
99
|
+
CLAUDE_SMART_USE_LOCAL_CLI_ENV,
|
|
100
|
+
"1",
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
"# Use the in-process ONNX embedder (chromadb) - no API key for semantic search",
|
|
104
|
+
CLAUDE_SMART_USE_LOCAL_EMBEDDING_ENV,
|
|
105
|
+
"1",
|
|
106
|
+
],
|
|
107
|
+
[null, CLAUDE_SMART_READ_ONLY_ENV, "0"],
|
|
108
|
+
];
|
|
109
|
+
const LOCAL_MODE_PRUNE_KEYS = new Set([
|
|
110
|
+
"REFLEXIO_URL",
|
|
111
|
+
"REFLEXIO_API_KEY",
|
|
112
|
+
REFLEXIO_USER_ID_ENV,
|
|
113
|
+
]);
|
|
93
114
|
|
|
94
115
|
function shouldCopyPath(src) {
|
|
95
116
|
const base = src.split(/[\\/]/).pop() || "";
|
|
@@ -216,6 +237,57 @@ function parseEnvLine(line) {
|
|
|
216
237
|
return { key, value };
|
|
217
238
|
}
|
|
218
239
|
|
|
240
|
+
function escapeEnvValue(value) {
|
|
241
|
+
return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function ensureLocalReflexioEnv() {
|
|
245
|
+
mkdirSync(dirname(REFLEXIO_ENV_PATH), { recursive: true });
|
|
246
|
+
const existing = existsSync(REFLEXIO_ENV_PATH)
|
|
247
|
+
? readFileSync(REFLEXIO_ENV_PATH, "utf8")
|
|
248
|
+
: "";
|
|
249
|
+
const present = new Set();
|
|
250
|
+
const keptLines = [];
|
|
251
|
+
let pruned = false;
|
|
252
|
+
for (const line of existing.split(/\r?\n/)) {
|
|
253
|
+
const parsed = parseEnvLine(line);
|
|
254
|
+
if (parsed) {
|
|
255
|
+
if (LOCAL_MODE_PRUNE_KEYS.has(parsed.key)) {
|
|
256
|
+
pruned = true;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
present.add(parsed.key);
|
|
260
|
+
}
|
|
261
|
+
keptLines.push(line);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const additions = [];
|
|
265
|
+
const added = [];
|
|
266
|
+
for (const [comment, key, value] of LOCAL_DEFAULT_ENV_ENTRIES) {
|
|
267
|
+
if (present.has(key)) continue;
|
|
268
|
+
if (comment) additions.push(comment);
|
|
269
|
+
if (key === CLAUDE_SMART_READ_ONLY_ENV) {
|
|
270
|
+
additions.push(`${key}="${escapeEnvValue(value)}"`);
|
|
271
|
+
} else {
|
|
272
|
+
additions.push(`${key}=${escapeEnvValue(value)}`);
|
|
273
|
+
}
|
|
274
|
+
added.push(key);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (additions.length > 0 || pruned) {
|
|
278
|
+
let content = keptLines.join("\n").replace(/\n*$/, "");
|
|
279
|
+
if (additions.length > 0) {
|
|
280
|
+
const prefix = content ? "\n" : "";
|
|
281
|
+
content = content + prefix + additions.join("\n");
|
|
282
|
+
}
|
|
283
|
+
writeFileSync(REFLEXIO_ENV_PATH, content ? `${content}\n` : "");
|
|
284
|
+
} else if (!existsSync(REFLEXIO_ENV_PATH)) {
|
|
285
|
+
writeFileSync(REFLEXIO_ENV_PATH, "");
|
|
286
|
+
}
|
|
287
|
+
chmodSync(REFLEXIO_ENV_PATH, 0o600);
|
|
288
|
+
return added;
|
|
289
|
+
}
|
|
290
|
+
|
|
219
291
|
function maskSecret(value) {
|
|
220
292
|
if (!value) return "";
|
|
221
293
|
if (value.length <= 8) return "*".repeat(value.length);
|
|
@@ -254,7 +326,12 @@ function loadReflexioSetupEnv() {
|
|
|
254
326
|
} else {
|
|
255
327
|
delete process.env.REFLEXIO_URL;
|
|
256
328
|
delete process.env.REFLEXIO_API_KEY;
|
|
329
|
+
delete process.env[REFLEXIO_USER_ID_ENV];
|
|
257
330
|
delete process.env[MANAGED_SETUP_ENV];
|
|
331
|
+
const added = ensureLocalReflexioEnv();
|
|
332
|
+
if (added.length > 0) {
|
|
333
|
+
process.stdout.write(`Seeded ${REFLEXIO_ENV_PATH} with ${added.join(", ")}.\n`);
|
|
334
|
+
}
|
|
258
335
|
}
|
|
259
336
|
const readOnly = ["1", "true", "yes", "on"].includes(
|
|
260
337
|
String(readOnlyValue).trim().toLowerCase(),
|
|
@@ -431,14 +508,14 @@ function runChecked(command, args, options = {}) {
|
|
|
431
508
|
});
|
|
432
509
|
}
|
|
433
510
|
|
|
434
|
-
function runPluginService(pluginRoot, scriptName, subcommand) {
|
|
511
|
+
function runPluginService(pluginRoot, scriptName, subcommand, envOverrides = {}) {
|
|
435
512
|
const script = join(pluginRoot, "scripts", scriptName);
|
|
436
513
|
if (!existsSync(script)) return false;
|
|
437
514
|
const bash = resolveCommand(isWindows() ? ["bash.exe", "bash"] : ["bash"]);
|
|
438
515
|
if (!bash) return false;
|
|
439
516
|
const result = spawnSync(bash, [script, subcommand], {
|
|
440
517
|
cwd: pluginRoot,
|
|
441
|
-
env: runtimeEnv(),
|
|
518
|
+
env: { ...runtimeEnv(), ...envOverrides },
|
|
442
519
|
stdio: "ignore",
|
|
443
520
|
windowsHide: true,
|
|
444
521
|
timeout: PLUGIN_SERVICE_TIMEOUT_MS,
|
|
@@ -465,6 +542,12 @@ function refreshDashboardService(pluginRoot) {
|
|
|
465
542
|
return runPluginService(pluginRoot, "dashboard-service.sh", "start");
|
|
466
543
|
}
|
|
467
544
|
|
|
545
|
+
function startBackendService(pluginRoot, host) {
|
|
546
|
+
return runPluginService(pluginRoot, "backend-service.sh", "start", {
|
|
547
|
+
CLAUDE_SMART_HOST: host,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
468
551
|
function stopClaudeSmartServices(pluginRoot) {
|
|
469
552
|
runPluginService(pluginRoot, "dashboard-service.sh", "stop");
|
|
470
553
|
runPluginService(pluginRoot, "backend-service.sh", "stop");
|
|
@@ -757,9 +840,7 @@ function patchCodexHooksForNode(pluginRoot, nodePath) {
|
|
|
757
840
|
const runner = join(pluginRoot, "scripts", "codex-hook.js");
|
|
758
841
|
const command = (...args) => [nodePath, runner, ...args].map(quoteCommandPart).join(" ");
|
|
759
842
|
// Dispatch by command content rather than index — entries can be added or
|
|
760
|
-
// reordered
|
|
761
|
-
// breaking the patch. Entries that must run as bash (smart-install.sh)
|
|
762
|
-
// are left untouched.
|
|
843
|
+
// reordered without breaking the patch.
|
|
763
844
|
const patchOne = (original) => {
|
|
764
845
|
if (typeof original !== "string") return original;
|
|
765
846
|
if (original.includes("smart-install.sh")) return original;
|
|
@@ -1435,6 +1516,9 @@ async function runInstall(args) {
|
|
|
1435
1516
|
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
1436
1517
|
}
|
|
1437
1518
|
process.stdout.write(`Prepared claude-smart runtime at ${pluginRoot}.\n`);
|
|
1519
|
+
if (startBackendService(pluginRoot, "claude-code")) {
|
|
1520
|
+
process.stdout.write("Started claude-smart backend service.\n");
|
|
1521
|
+
}
|
|
1438
1522
|
if (refreshDashboardService(pluginRoot)) {
|
|
1439
1523
|
process.stdout.write("Refreshed claude-smart dashboard service.\n");
|
|
1440
1524
|
}
|
|
@@ -1515,6 +1599,9 @@ async function runInstallCodex(args) {
|
|
|
1515
1599
|
if (readOnly) {
|
|
1516
1600
|
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
1517
1601
|
}
|
|
1602
|
+
if (startBackendService(cacheDir, "codex")) {
|
|
1603
|
+
process.stdout.write("Started claude-smart backend service.\n");
|
|
1604
|
+
}
|
|
1518
1605
|
if (refreshDashboardService(cacheDir)) {
|
|
1519
1606
|
process.stdout.write("Refreshed claude-smart dashboard service.\n");
|
|
1520
1607
|
}
|
package/package.json
CHANGED
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
{
|
|
6
6
|
"matcher": "startup|resume",
|
|
7
7
|
"hooks": [
|
|
8
|
-
{
|
|
9
|
-
"type": "command",
|
|
10
|
-
"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/smart-install.sh\" || true",
|
|
11
|
-
"timeout": 300
|
|
12
|
-
},
|
|
13
8
|
{
|
|
14
9
|
"type": "command",
|
|
15
10
|
"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/ensure-plugin-root.sh\" \"$_R\" || true",
|
package/plugin/hooks/hooks.json
CHANGED
|
@@ -14,16 +14,6 @@
|
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
16
|
"SessionStart": [
|
|
17
|
-
{
|
|
18
|
-
"matcher": "startup|clear|compact|resume",
|
|
19
|
-
"hooks": [
|
|
20
|
-
{
|
|
21
|
-
"type": "command",
|
|
22
|
-
"command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/smart-install.sh\"",
|
|
23
|
-
"timeout": 300
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
},
|
|
27
17
|
{
|
|
28
18
|
"matcher": "startup|clear|compact|resume",
|
|
29
19
|
"hooks": [
|
package/plugin/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "claude-smart"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.41"
|
|
4
4
|
description = "Self-improving Claude Code plugin — learns from corrections via reflexio"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -15,6 +15,10 @@ dependencies = [
|
|
|
15
15
|
# downloaded on first use, not at install time.
|
|
16
16
|
"chromadb>=0.5",
|
|
17
17
|
"einops>=0.8.0",
|
|
18
|
+
# Native SQLite vector KNN for local storage. Reflexio can fall back to
|
|
19
|
+
# Python ranking without this, but installed claude-smart should include
|
|
20
|
+
# the accelerator by default.
|
|
21
|
+
"sqlite-vec>=0.1.6",
|
|
18
22
|
]
|
|
19
23
|
|
|
20
24
|
[project.scripts]
|
|
@@ -201,13 +201,13 @@ case "$CMD" in
|
|
|
201
201
|
fi
|
|
202
202
|
if ! command -v uv >/dev/null 2>&1; then
|
|
203
203
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
204
|
-
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH;
|
|
205
|
-
CLAUDE_SMART_BOOTSTRAPPING=1
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; starting installer in background"
|
|
205
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
206
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
207
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
208
208
|
fi
|
|
209
209
|
if ! command -v uv >/dev/null 2>&1; then
|
|
210
|
-
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH
|
|
210
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; installer recovery scheduled; skipping"
|
|
211
211
|
emit_ok; exit 0
|
|
212
212
|
fi
|
|
213
213
|
fi
|
|
@@ -302,33 +302,6 @@ function detached(command, args, options = {}) {
|
|
|
302
302
|
return child.pid;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
function runInstaller(root, reason) {
|
|
306
|
-
if (process.env.CLAUDE_SMART_BOOTSTRAPPING === "1") return false;
|
|
307
|
-
const script = path.join(root, "scripts", "smart-install.sh");
|
|
308
|
-
if (!fs.existsSync(script)) return false;
|
|
309
|
-
const bash = bashPath();
|
|
310
|
-
if (!bash) return false;
|
|
311
|
-
appendLog("backend.log", `[claude-smart] ${reason}; running installer`);
|
|
312
|
-
const result = spawnSync(bash, [script], {
|
|
313
|
-
cwd: root,
|
|
314
|
-
env: {
|
|
315
|
-
...process.env,
|
|
316
|
-
CLAUDE_SMART_BOOTSTRAPPING: "1",
|
|
317
|
-
},
|
|
318
|
-
encoding: "utf8",
|
|
319
|
-
maxBuffer: 20 * 1024 * 1024,
|
|
320
|
-
windowsHide: true,
|
|
321
|
-
});
|
|
322
|
-
const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
323
|
-
if (output) {
|
|
324
|
-
ensureDir(STATE_DIR);
|
|
325
|
-
fs.appendFileSync(path.join(STATE_DIR, "install.log"), `${output}\n`);
|
|
326
|
-
trimLog(path.join(STATE_DIR, "install.log"));
|
|
327
|
-
}
|
|
328
|
-
prependRuntimePath();
|
|
329
|
-
return result.status === 0;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
305
|
function startInstallerDetached(root, reason) {
|
|
333
306
|
if (process.env.CLAUDE_SMART_BOOTSTRAPPING === "1") return false;
|
|
334
307
|
const script = path.join(root, "scripts", "smart-install.sh");
|
|
@@ -410,11 +383,11 @@ async function startBackend(root) {
|
|
|
410
383
|
}
|
|
411
384
|
const uv = uvPath();
|
|
412
385
|
if (!uv) {
|
|
413
|
-
|
|
386
|
+
startInstallerDetached(root, "backend: uv not on PATH");
|
|
414
387
|
}
|
|
415
388
|
const readyUv = uvPath();
|
|
416
389
|
if (!readyUv) {
|
|
417
|
-
appendLog("backend.log", "[claude-smart] backend: uv not on PATH
|
|
390
|
+
appendLog("backend.log", "[claude-smart] backend: uv not on PATH; installer recovery scheduled; skipping");
|
|
418
391
|
emitOk();
|
|
419
392
|
return;
|
|
420
393
|
}
|
|
@@ -478,11 +451,11 @@ async function startDashboard(root) {
|
|
|
478
451
|
}
|
|
479
452
|
const npm = npmPath();
|
|
480
453
|
if (!npm) {
|
|
481
|
-
|
|
454
|
+
startInstallerDetached(root, "dashboard: npm not on PATH");
|
|
482
455
|
}
|
|
483
456
|
const readyNpm = npmPath();
|
|
484
457
|
if (!readyNpm) {
|
|
485
|
-
appendLog("dashboard.log", "[claude-smart] dashboard: npm not on PATH
|
|
458
|
+
appendLog("dashboard.log", "[claude-smart] dashboard: npm not on PATH; installer recovery scheduled; skipping");
|
|
486
459
|
emitOk();
|
|
487
460
|
return;
|
|
488
461
|
}
|
|
@@ -512,14 +485,10 @@ function runHook(root, event) {
|
|
|
512
485
|
trimLog(path.join(STATE_DIR, "backend.log"));
|
|
513
486
|
let uv = uvPath();
|
|
514
487
|
if (!uv) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
uv = uvPath();
|
|
518
|
-
} else {
|
|
519
|
-
startInstallerDetached(root, "hook: uv not on PATH");
|
|
520
|
-
}
|
|
488
|
+
startInstallerDetached(root, "hook: uv not on PATH");
|
|
489
|
+
uv = uvPath();
|
|
521
490
|
if (!uv) {
|
|
522
|
-
appendLog("backend.log", "[claude-smart] hook: uv not on PATH
|
|
491
|
+
appendLog("backend.log", "[claude-smart] hook: uv not on PATH; installer recovery scheduled; skipping");
|
|
523
492
|
emitHookOk();
|
|
524
493
|
return 0;
|
|
525
494
|
}
|
|
@@ -535,7 +504,7 @@ function runHook(root, event) {
|
|
|
535
504
|
REFLEXIO_URL: readBackendUrl(),
|
|
536
505
|
CLAUDE_SMART_HOST: "codex",
|
|
537
506
|
CLAUDE_SMART_CLI_PATH: process.env.CLAUDE_SMART_CLI_PATH || codexCompatPath(root),
|
|
538
|
-
CLAUDE_SMART_CITATION_LINK_STYLE: process.env.CLAUDE_SMART_CITATION_LINK_STYLE || "
|
|
507
|
+
CLAUDE_SMART_CITATION_LINK_STYLE: process.env.CLAUDE_SMART_CITATION_LINK_STYLE || "markdown",
|
|
539
508
|
},
|
|
540
509
|
input,
|
|
541
510
|
stdio: ["pipe", "pipe", "inherit"],
|
|
@@ -190,14 +190,13 @@ case "$CMD" in
|
|
|
190
190
|
NPM_BIN=$(claude_smart_resolve_npm || true)
|
|
191
191
|
if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
|
|
192
192
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
193
|
-
echo "[claude-smart] dashboard: npm is not on PATH;
|
|
194
|
-
CLAUDE_SMART_BOOTSTRAPPING=1
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
NPM_BIN=$(claude_smart_resolve_npm || true)
|
|
193
|
+
echo "[claude-smart] dashboard: npm is not on PATH; starting installer in background" >>"$LOG_FILE"
|
|
194
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
195
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
196
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
198
197
|
fi
|
|
199
198
|
if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
|
|
200
|
-
reason="npm is not on PATH
|
|
199
|
+
reason="npm is not on PATH; installer recovery scheduled; dashboard cannot start yet"
|
|
201
200
|
echo "[claude-smart] dashboard: $reason; skipping" >>"$LOG_FILE"
|
|
202
201
|
claude_smart_write_dashboard_unavailable "$reason"
|
|
203
202
|
emit_ok; exit 0
|
|
@@ -21,7 +21,7 @@ case "$EVENT" in
|
|
|
21
21
|
esac
|
|
22
22
|
export CLAUDE_SMART_HOST="$HOST"
|
|
23
23
|
if [ "$HOST" = "codex" ] && [ -z "${CLAUDE_SMART_CITATION_LINK_STYLE:-}" ]; then
|
|
24
|
-
export CLAUDE_SMART_CITATION_LINK_STYLE="
|
|
24
|
+
export CLAUDE_SMART_CITATION_LINK_STYLE="markdown"
|
|
25
25
|
fi
|
|
26
26
|
|
|
27
27
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
@@ -59,8 +59,9 @@ if [ -f "$FAILURE_MARKER" ]; then
|
|
|
59
59
|
if [ -z "$stored_fp" ] || [ "$stored_fp" != "$current_fp" ]; then
|
|
60
60
|
rm -f "$FAILURE_MARKER"
|
|
61
61
|
else
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
failure_py="$(claude_smart_resolve_python 2>/dev/null || true)"
|
|
63
|
+
if [ "$EVENT" = "session-start" ] && [ -n "$failure_py" ]; then
|
|
64
|
+
"$failure_py" - "$FAILURE_MARKER" <<'PY'
|
|
64
65
|
import json, pathlib, sys
|
|
65
66
|
first = pathlib.Path(sys.argv[1]).read_text().splitlines()
|
|
66
67
|
msg = (first[0].strip() if first else "") or "unknown error"
|
|
@@ -84,29 +85,18 @@ PY
|
|
|
84
85
|
fi
|
|
85
86
|
|
|
86
87
|
if ! command -v uv >/dev/null 2>&1; then
|
|
87
|
-
# Self-heal from skipped
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# dependency setup.
|
|
88
|
+
# Self-heal from skipped setup without blocking the first prompt. The
|
|
89
|
+
# installer and Setup hook own dependency bootstrap; hooks only schedule
|
|
90
|
+
# recovery and return so SessionStart stays lightweight.
|
|
91
91
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" = "1" ]; then
|
|
92
92
|
claude_smart_emit_continue
|
|
93
93
|
exit 0
|
|
94
94
|
fi
|
|
95
95
|
if [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
96
96
|
mkdir -p "$STATE_DIR"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
claude_smart_prepend_node_bins
|
|
101
|
-
if command -v uv >/dev/null 2>&1; then
|
|
102
|
-
bash "$HERE/backend-service.sh" start >/dev/null 2>&1 || true
|
|
103
|
-
bash "$HERE/dashboard-service.sh" start >/dev/null 2>&1 || true
|
|
104
|
-
fi
|
|
105
|
-
else
|
|
106
|
-
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
107
|
-
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
108
|
-
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
109
|
-
fi
|
|
97
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
98
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
99
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
110
100
|
fi
|
|
111
101
|
if ! command -v uv >/dev/null 2>&1; then
|
|
112
102
|
claude_smart_emit_continue
|
|
@@ -127,17 +117,13 @@ ensure_hook_package_importable() {
|
|
|
127
117
|
fi
|
|
128
118
|
|
|
129
119
|
mkdir -p "$STATE_DIR"
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
138
|
-
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
139
|
-
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
140
|
-
fi
|
|
120
|
+
{
|
|
121
|
+
printf '%s\n' "[claude-smart] hook: claude_smart.hook is not importable; retrying install in background"
|
|
122
|
+
date 2>/dev/null || true
|
|
123
|
+
} >>"$STATE_DIR/install.log" 2>&1
|
|
124
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
125
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
126
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
141
127
|
|
|
142
128
|
claude_smart_python_imports "$PLUGIN_ROOT" claude_smart.hook
|
|
143
129
|
}
|
|
@@ -106,12 +106,25 @@ install_complete() {
|
|
|
106
106
|
command -v uv >/dev/null 2>&1 || return 1
|
|
107
107
|
[ -d "$PLUGIN_ROOT/.venv" ] || return 1
|
|
108
108
|
claude_smart_python_imports "$PLUGIN_ROOT" claude_smart.hook || return 1
|
|
109
|
+
if [ -z "${REFLEXIO_API_KEY:-}" ]; then
|
|
110
|
+
[ -f "$HOME/.reflexio/.env" ] || return 1
|
|
111
|
+
grep -qE '^(export[[:space:]]+)?CLAUDE_SMART_USE_LOCAL_CLI=' "$HOME/.reflexio/.env" || return 1
|
|
112
|
+
grep -qE '^(export[[:space:]]+)?CLAUDE_SMART_USE_LOCAL_EMBEDDING=' "$HOME/.reflexio/.env" || return 1
|
|
113
|
+
grep -qE '^(export[[:space:]]+)?CLAUDE_SMART_READ_ONLY=' "$HOME/.reflexio/.env" || return 1
|
|
114
|
+
fi
|
|
109
115
|
if [ -d "$PLUGIN_ROOT/dashboard" ]; then
|
|
110
116
|
[ -d "$PLUGIN_ROOT/dashboard/.next" ] || [ -f "$MARKER_DIR/dashboard-build.pid" ] || [ -f "$(claude_smart_dashboard_unavailable_marker)" ] || return 1
|
|
111
117
|
fi
|
|
112
118
|
return 0
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
start_backend_service() {
|
|
122
|
+
if [ -x "$HERE/backend-service.sh" ]; then
|
|
123
|
+
echo "[claude-smart] starting backend service in background" >&2
|
|
124
|
+
bash "$HERE/backend-service.sh" start >/dev/null 2>&1 || true
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
115
128
|
install_vendored_reflexio() {
|
|
116
129
|
local VENDORED_REFLEXIO PLUGIN_PYTHON
|
|
117
130
|
|
|
@@ -385,6 +398,7 @@ fi
|
|
|
385
398
|
preflight_supported_runtime_platform
|
|
386
399
|
|
|
387
400
|
if install_complete; then
|
|
401
|
+
start_backend_service
|
|
388
402
|
claude_smart_emit_continue
|
|
389
403
|
exit 0
|
|
390
404
|
fi
|
|
@@ -491,6 +505,63 @@ claude_smart_env_upsert() {
|
|
|
491
505
|
fi
|
|
492
506
|
}
|
|
493
507
|
|
|
508
|
+
claude_smart_env_append_raw_if_missing() {
|
|
509
|
+
local key value
|
|
510
|
+
key="$1"
|
|
511
|
+
value="$2"
|
|
512
|
+
if ! grep -qE "^(export[[:space:]]+)?${key}=" "$REFLEXIO_ENV"; then
|
|
513
|
+
printf '%s=%s\n' "$key" "$value" >> "$REFLEXIO_ENV"
|
|
514
|
+
fi
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
claude_smart_prune_managed_env_keys_for_local() {
|
|
518
|
+
local tmp line raw_key key
|
|
519
|
+
[ -f "$REFLEXIO_ENV" ] || return 0
|
|
520
|
+
tmp="$(mktemp "${REFLEXIO_ENV}.tmp.XXXXXX")"
|
|
521
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
522
|
+
raw_key="$line"
|
|
523
|
+
raw_key="${raw_key#"${raw_key%%[![:space:]]*}"}"
|
|
524
|
+
case "$raw_key" in
|
|
525
|
+
export\ *) raw_key="${raw_key#export }" ;;
|
|
526
|
+
esac
|
|
527
|
+
key="${raw_key%%=*}"
|
|
528
|
+
key="${key%"${key##*[![:space:]]}"}"
|
|
529
|
+
case "$key" in
|
|
530
|
+
REFLEXIO_URL|REFLEXIO_API_KEY|REFLEXIO_USER_ID)
|
|
531
|
+
continue
|
|
532
|
+
;;
|
|
533
|
+
esac
|
|
534
|
+
printf '%s\n' "$line" >> "$tmp"
|
|
535
|
+
done < "$REFLEXIO_ENV"
|
|
536
|
+
mv "$tmp" "$REFLEXIO_ENV"
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
claude_smart_ensure_local_env_defaults() {
|
|
540
|
+
[ -z "${REFLEXIO_API_KEY:-}" ] || return 0
|
|
541
|
+
mkdir -p "$(dirname "$REFLEXIO_ENV")"
|
|
542
|
+
touch "$REFLEXIO_ENV"
|
|
543
|
+
chmod 600 "$REFLEXIO_ENV"
|
|
544
|
+
claude_smart_prune_managed_env_keys_for_local
|
|
545
|
+
unset REFLEXIO_URL REFLEXIO_API_KEY REFLEXIO_USER_ID CLAUDE_SMART_MANAGED_SETUP
|
|
546
|
+
if ! grep -qE '^(export[[:space:]]+)?CLAUDE_SMART_USE_LOCAL_CLI=' "$REFLEXIO_ENV"; then
|
|
547
|
+
printf '# Route reflexio generation through the local Claude Code CLI\n' >> "$REFLEXIO_ENV"
|
|
548
|
+
claude_smart_env_append_raw_if_missing CLAUDE_SMART_USE_LOCAL_CLI "1"
|
|
549
|
+
echo "[claude-smart] appended CLAUDE_SMART_USE_LOCAL_CLI=1 to $REFLEXIO_ENV" >&2
|
|
550
|
+
fi
|
|
551
|
+
if ! grep -qE '^(export[[:space:]]+)?CLAUDE_SMART_USE_LOCAL_EMBEDDING=' "$REFLEXIO_ENV"; then
|
|
552
|
+
printf '# Use the in-process ONNX embedder (chromadb) - no API key for semantic search\n' >> "$REFLEXIO_ENV"
|
|
553
|
+
claude_smart_env_append_raw_if_missing CLAUDE_SMART_USE_LOCAL_EMBEDDING "1"
|
|
554
|
+
echo "[claude-smart] appended CLAUDE_SMART_USE_LOCAL_EMBEDDING=1 to $REFLEXIO_ENV" >&2
|
|
555
|
+
fi
|
|
556
|
+
if ! grep -qE '^(export[[:space:]]+)?CLAUDE_SMART_READ_ONLY=' "$REFLEXIO_ENV"; then
|
|
557
|
+
claude_smart_env_upsert CLAUDE_SMART_READ_ONLY "0"
|
|
558
|
+
echo "[claude-smart] appended CLAUDE_SMART_READ_ONLY=0 to $REFLEXIO_ENV" >&2
|
|
559
|
+
fi
|
|
560
|
+
chmod 600 "$REFLEXIO_ENV"
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
claude_smart_ensure_local_env_defaults
|
|
564
|
+
|
|
494
565
|
# Migrate stale REFLEXIO_URL from reflexio's library default (8081) to the
|
|
495
566
|
# plugin backend port (8071). Matches the quoted and unquoted forms but
|
|
496
567
|
# requires paired quotes, so malformed or deliberately different values
|
|
@@ -561,6 +632,8 @@ if ! bash "$HERE/ensure-plugin-root.sh" "$PLUGIN_ROOT"; then
|
|
|
561
632
|
echo "[claude-smart] WARNING: failed to set ~/.reflexio/plugin-root symlink — slash commands may not resolve" >&2
|
|
562
633
|
fi
|
|
563
634
|
|
|
635
|
+
start_backend_service
|
|
636
|
+
|
|
564
637
|
write_success_marker
|
|
565
|
-
echo "[claude-smart] install complete. Backend
|
|
638
|
+
echo "[claude-smart] install complete. Backend started; dashboard auto-starts on session start." >&2
|
|
566
639
|
claude_smart_emit_continue
|
|
@@ -894,7 +894,7 @@ def _register_codex_marketplace(root: Path) -> tuple[bool, str]:
|
|
|
894
894
|
|
|
895
895
|
|
|
896
896
|
def _configure_reflexio_setup() -> bool:
|
|
897
|
-
"""Load setup state
|
|
897
|
+
"""Load setup state and ensure local defaults when unmanaged.
|
|
898
898
|
|
|
899
899
|
Returns:
|
|
900
900
|
bool: Whether read-only mode is enabled.
|
|
@@ -939,7 +939,11 @@ def _configure_reflexio_setup() -> bool:
|
|
|
939
939
|
else:
|
|
940
940
|
os.environ.pop(env_config.REFLEXIO_URL_ENV, None)
|
|
941
941
|
os.environ.pop(env_config.REFLEXIO_API_KEY_ENV, None)
|
|
942
|
+
os.environ.pop("REFLEXIO_USER_ID", None)
|
|
942
943
|
os.environ.pop("CLAUDE_SMART_MANAGED_SETUP", None)
|
|
944
|
+
added = env_config.ensure_local_env_defaults(_REFLEXIO_ENV_PATH)
|
|
945
|
+
if added:
|
|
946
|
+
sys.stdout.write(f"Seeded {_REFLEXIO_ENV_PATH} with {', '.join(added)}.\n")
|
|
943
947
|
return read_only
|
|
944
948
|
|
|
945
949
|
|
|
@@ -1342,7 +1346,9 @@ def _run_service(script: Path, subcmd: str) -> int:
|
|
|
1342
1346
|
return 1
|
|
1343
1347
|
bash = _resolve_bash()
|
|
1344
1348
|
if not bash:
|
|
1345
|
-
sys.stderr.write(
|
|
1349
|
+
sys.stderr.write(
|
|
1350
|
+
"error: bash is required to run claude-smart service scripts\n"
|
|
1351
|
+
)
|
|
1346
1352
|
return 1
|
|
1347
1353
|
try:
|
|
1348
1354
|
subprocess.run([bash, str(script), subcmd], check=True)
|
|
@@ -238,6 +238,11 @@ def render_inline_compact_with_registry(
|
|
|
238
238
|
item = f"{content} (title: {title}"
|
|
239
239
|
if rule_url:
|
|
240
240
|
item += f"; open: {rule_url}"
|
|
241
|
+
marker_parts.append(
|
|
242
|
+
_markdown_link(
|
|
243
|
+
rule_url, _strip_trailing_sentence_punctuation(title)
|
|
244
|
+
)
|
|
245
|
+
)
|
|
241
246
|
item += ")"
|
|
242
247
|
if entry.get("kind") == "profile":
|
|
243
248
|
preference_parts.append(item)
|
|
@@ -280,6 +285,17 @@ def _compact_citation_instruction(marker_parts: list[str] | None = None) -> str:
|
|
|
280
285
|
"the same linked memory text; keep the link, but do not show the "
|
|
281
286
|
"URL. Skip when unrelated."
|
|
282
287
|
)
|
|
288
|
+
if marker_parts:
|
|
289
|
+
marker = f"✨ claude-smart rule applied: {' | '.join(marker_parts)}"
|
|
290
|
+
separator_instruction = (
|
|
291
|
+
" Separate multiple linked memories with the visible ` | ` separator."
|
|
292
|
+
if len(marker_parts) > 1
|
|
293
|
+
else ""
|
|
294
|
+
)
|
|
295
|
+
return _remoteize_citation_instruction(
|
|
296
|
+
f"If used, copy this final marker exactly with markdown links: "
|
|
297
|
+
f"`{marker}`.{separator_instruction} Skip when unrelated."
|
|
298
|
+
)
|
|
283
299
|
return _remoteize_citation_instruction(
|
|
284
300
|
"Only if a listed [cs:...] item materially changes your answer, end "
|
|
285
301
|
"with one final marker like `✨ claude-smart rule applied: "
|
|
@@ -368,7 +384,7 @@ def _append_playbook_bullet(
|
|
|
368
384
|
item_id = cs_cite.rank_id("playbook", rank, real_id)
|
|
369
385
|
title = _title_from_content(content)
|
|
370
386
|
dashboard_url = _dashboard_url("playbook", real_id, source_kind)
|
|
371
|
-
rule_url = _rule_url(item_id, "playbook")
|
|
387
|
+
rule_url = _rule_url(item_id, "playbook", real_id, source_kind)
|
|
372
388
|
bullet = f"- [cs:{item_id}] {content}"
|
|
373
389
|
if trigger:
|
|
374
390
|
bullet += f" _(when: {trigger})_"
|
|
@@ -407,7 +423,7 @@ def _format_profiles(
|
|
|
407
423
|
item_id = cs_cite.rank_id("profile", rank, real_id)
|
|
408
424
|
title = _title_from_content(content)
|
|
409
425
|
dashboard_url = _dashboard_url("profile", real_id)
|
|
410
|
-
rule_url = _rule_url(item_id, "profile")
|
|
426
|
+
rule_url = _rule_url(item_id, "profile", real_id)
|
|
411
427
|
bullet = f"- [cs:{item_id}] {content}"
|
|
412
428
|
if rule_url:
|
|
413
429
|
bullet += f" _(open: {rule_url})_"
|
|
@@ -427,7 +443,7 @@ def _format_profiles(
|
|
|
427
443
|
|
|
428
444
|
|
|
429
445
|
def _dashboard_url(kind: str, real_id: Any, source_kind: str | None = None) -> str:
|
|
430
|
-
remote_url =
|
|
446
|
+
remote_url = _remote_reflexio_item_url(kind, real_id, source_kind)
|
|
431
447
|
if remote_url:
|
|
432
448
|
return remote_url
|
|
433
449
|
if real_id is None:
|
|
@@ -442,8 +458,10 @@ def _dashboard_url(kind: str, real_id: Any, source_kind: str | None = None) -> s
|
|
|
442
458
|
return ""
|
|
443
459
|
|
|
444
460
|
|
|
445
|
-
def _rule_url(
|
|
446
|
-
|
|
461
|
+
def _rule_url(
|
|
462
|
+
item_id: str, kind: str, real_id: Any = None, source_kind: str | None = None
|
|
463
|
+
) -> str:
|
|
464
|
+
remote_url = _remote_reflexio_item_url(kind, real_id, source_kind)
|
|
447
465
|
if remote_url:
|
|
448
466
|
return remote_url
|
|
449
467
|
if not item_id:
|
|
@@ -453,6 +471,27 @@ def _rule_url(item_id: str, kind: str) -> str:
|
|
|
453
471
|
return f"{base}/rules/{encoded_id}"
|
|
454
472
|
|
|
455
473
|
|
|
474
|
+
def _remote_reflexio_item_url(
|
|
475
|
+
kind: str, real_id: Any, source_kind: str | None = None
|
|
476
|
+
) -> str:
|
|
477
|
+
origin = _remote_reflexio_origin()
|
|
478
|
+
if not origin:
|
|
479
|
+
return ""
|
|
480
|
+
if real_id is None:
|
|
481
|
+
return _remote_reflexio_page_url(kind)
|
|
482
|
+
encoded_id = quote(str(real_id), safe="")
|
|
483
|
+
if kind == "profile":
|
|
484
|
+
return f"{origin}/profiles?profile_id={encoded_id}"
|
|
485
|
+
if kind == "playbook":
|
|
486
|
+
if source_kind == "user_playbook":
|
|
487
|
+
return (
|
|
488
|
+
f"{origin}/playbooks?resource=user_playbook&"
|
|
489
|
+
f"user_playbook_id={encoded_id}"
|
|
490
|
+
)
|
|
491
|
+
return f"{origin}/playbooks?agent_playbook_id={encoded_id}"
|
|
492
|
+
return ""
|
|
493
|
+
|
|
494
|
+
|
|
456
495
|
def _remote_reflexio_page_url(kind: str) -> str:
|
|
457
496
|
origin = _remote_reflexio_origin()
|
|
458
497
|
if not origin:
|
|
@@ -504,6 +543,12 @@ def _osc8_link(url: str, label: str) -> str:
|
|
|
504
543
|
return f"\x1b]8;;{url}\x1b\\{label}\x1b]8;;\x1b\\"
|
|
505
544
|
|
|
506
545
|
|
|
546
|
+
def _markdown_link(url: str, label: str) -> str:
|
|
547
|
+
safe_label = label.replace("[", "\\[").replace("]", "\\]")
|
|
548
|
+
safe_url = url.replace(")", "%29")
|
|
549
|
+
return f"[{safe_label}]({safe_url})"
|
|
550
|
+
|
|
551
|
+
|
|
507
552
|
def _strip_trailing_sentence_punctuation(text: str) -> str:
|
|
508
553
|
return text.rstrip().rstrip(".!?")
|
|
509
554
|
|
|
@@ -39,7 +39,7 @@ from __future__ import annotations
|
|
|
39
39
|
|
|
40
40
|
import re
|
|
41
41
|
from typing import Any
|
|
42
|
-
from urllib.parse import unquote, urlparse
|
|
42
|
+
from urllib.parse import parse_qs, unquote, urlparse
|
|
43
43
|
|
|
44
44
|
_FINGERPRINT_LEN = 4
|
|
45
45
|
|
|
@@ -287,6 +287,17 @@ def dashboard_url_token(url: str) -> str:
|
|
|
287
287
|
parsed = urlparse(url)
|
|
288
288
|
path = parsed.path if parsed.scheme else url.split("?", 1)[0].split("#", 1)[0]
|
|
289
289
|
parts = [unquote(part) for part in path.strip("/").split("/") if part]
|
|
290
|
+
query = parse_qs(parsed.query)
|
|
291
|
+
if len(parts) == 1 and parts[0] == "profiles":
|
|
292
|
+
profile_ids = query.get("profile_id") or []
|
|
293
|
+
return f"route:profile:profile:{profile_ids[0]}" if profile_ids else ""
|
|
294
|
+
if len(parts) == 1 and parts[0] == "playbooks":
|
|
295
|
+
user_playbook_ids = query.get("user_playbook_id") or []
|
|
296
|
+
if query.get("resource") == ["user_playbook"] and user_playbook_ids:
|
|
297
|
+
return f"route:playbook:user_playbook:{user_playbook_ids[0]}"
|
|
298
|
+
agent_playbook_ids = query.get("agent_playbook_id") or []
|
|
299
|
+
if agent_playbook_ids:
|
|
300
|
+
return f"route:playbook:agent_playbook:{agent_playbook_ids[0]}"
|
|
290
301
|
if len(parts) == 3 and parts[0] == "skills" and parts[1] in {"project", "shared"}:
|
|
291
302
|
source_kind = "user_playbook" if parts[1] == "project" else "agent_playbook"
|
|
292
303
|
return f"route:playbook:{source_kind}:{parts[2]}"
|
|
@@ -10,6 +10,27 @@ MANAGED_REFLEXIO_URL = "https://www.reflexio.ai/"
|
|
|
10
10
|
REFLEXIO_URL_ENV = "REFLEXIO_URL"
|
|
11
11
|
REFLEXIO_API_KEY_ENV = "REFLEXIO_API_KEY"
|
|
12
12
|
CLAUDE_SMART_READ_ONLY_ENV = "CLAUDE_SMART_READ_ONLY"
|
|
13
|
+
CLAUDE_SMART_USE_LOCAL_CLI_ENV = "CLAUDE_SMART_USE_LOCAL_CLI"
|
|
14
|
+
CLAUDE_SMART_USE_LOCAL_EMBEDDING_ENV = "CLAUDE_SMART_USE_LOCAL_EMBEDDING"
|
|
15
|
+
|
|
16
|
+
_LOCAL_DEFAULT_ENTRIES = (
|
|
17
|
+
(
|
|
18
|
+
"# Route reflexio generation through the local Claude Code CLI",
|
|
19
|
+
CLAUDE_SMART_USE_LOCAL_CLI_ENV,
|
|
20
|
+
"1",
|
|
21
|
+
),
|
|
22
|
+
(
|
|
23
|
+
"# Use the in-process ONNX embedder (chromadb) - no API key for semantic search",
|
|
24
|
+
CLAUDE_SMART_USE_LOCAL_EMBEDDING_ENV,
|
|
25
|
+
"1",
|
|
26
|
+
),
|
|
27
|
+
(None, CLAUDE_SMART_READ_ONLY_ENV, "0"),
|
|
28
|
+
)
|
|
29
|
+
_LOCAL_MODE_PRUNE_KEYS = {
|
|
30
|
+
REFLEXIO_URL_ENV,
|
|
31
|
+
REFLEXIO_API_KEY_ENV,
|
|
32
|
+
"REFLEXIO_USER_ID",
|
|
33
|
+
}
|
|
13
34
|
|
|
14
35
|
|
|
15
36
|
def parse_env_line(line: str) -> tuple[str, str] | None:
|
|
@@ -89,6 +110,59 @@ def set_env_vars(path: Path, values: dict[str, str]) -> list[str]:
|
|
|
89
110
|
return added
|
|
90
111
|
|
|
91
112
|
|
|
113
|
+
def ensure_local_env_defaults(path: Path | None = None) -> list[str]:
|
|
114
|
+
"""Create or augment ``~/.reflexio/.env`` for claude-smart local mode.
|
|
115
|
+
|
|
116
|
+
Existing active assignments win. This repairs first installs and deleted
|
|
117
|
+
env files without clobbering explicit user overrides such as
|
|
118
|
+
``CLAUDE_SMART_READ_ONLY=1``.
|
|
119
|
+
"""
|
|
120
|
+
path = path or REFLEXIO_ENV_PATH
|
|
121
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
try:
|
|
123
|
+
existing = path.read_text()
|
|
124
|
+
except OSError:
|
|
125
|
+
existing = ""
|
|
126
|
+
|
|
127
|
+
present: set[str] = set()
|
|
128
|
+
kept_lines: list[str] = []
|
|
129
|
+
pruned = False
|
|
130
|
+
for line in existing.splitlines():
|
|
131
|
+
parsed = parse_env_line(line)
|
|
132
|
+
if parsed is not None:
|
|
133
|
+
key, _value = parsed
|
|
134
|
+
if key in _LOCAL_MODE_PRUNE_KEYS:
|
|
135
|
+
pruned = True
|
|
136
|
+
continue
|
|
137
|
+
present.add(key)
|
|
138
|
+
kept_lines.append(line)
|
|
139
|
+
|
|
140
|
+
additions: list[str] = []
|
|
141
|
+
added_keys: list[str] = []
|
|
142
|
+
for comment, key, value in _LOCAL_DEFAULT_ENTRIES:
|
|
143
|
+
if key in present:
|
|
144
|
+
continue
|
|
145
|
+
if comment:
|
|
146
|
+
additions.append(comment)
|
|
147
|
+
if key == CLAUDE_SMART_READ_ONLY_ENV:
|
|
148
|
+
additions.append(f'{key}="{_escape_env_value(value)}"')
|
|
149
|
+
else:
|
|
150
|
+
additions.append(f"{key}={_escape_env_value(value)}")
|
|
151
|
+
added_keys.append(key)
|
|
152
|
+
|
|
153
|
+
if additions or pruned:
|
|
154
|
+
content = "\n".join(kept_lines)
|
|
155
|
+
if additions:
|
|
156
|
+
prefix = "" if not content or content.endswith("\n") else "\n"
|
|
157
|
+
content = content + prefix + "\n".join(additions)
|
|
158
|
+
content = content + ("\n" if content else "")
|
|
159
|
+
path.write_text(content, encoding="utf-8")
|
|
160
|
+
elif not path.exists():
|
|
161
|
+
path.touch()
|
|
162
|
+
path.chmod(0o600)
|
|
163
|
+
return added_keys
|
|
164
|
+
|
|
165
|
+
|
|
92
166
|
def env_truthy(name: str) -> bool:
|
|
93
167
|
"""Return True when an environment flag is explicitly enabled."""
|
|
94
168
|
return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
|
package/plugin/uv.lock
CHANGED
|
@@ -419,12 +419,13 @@ wheels = [
|
|
|
419
419
|
|
|
420
420
|
[[package]]
|
|
421
421
|
name = "claude-smart"
|
|
422
|
-
version = "0.2.
|
|
422
|
+
version = "0.2.41"
|
|
423
423
|
source = { editable = "." }
|
|
424
424
|
dependencies = [
|
|
425
425
|
{ name = "chromadb" },
|
|
426
426
|
{ name = "einops" },
|
|
427
427
|
{ name = "reflexio-ai" },
|
|
428
|
+
{ name = "sqlite-vec" },
|
|
428
429
|
]
|
|
429
430
|
|
|
430
431
|
[package.dev-dependencies]
|
|
@@ -437,6 +438,7 @@ requires-dist = [
|
|
|
437
438
|
{ name = "chromadb", specifier = ">=0.5" },
|
|
438
439
|
{ name = "einops", specifier = ">=0.8.0" },
|
|
439
440
|
{ name = "reflexio-ai", specifier = ">=0.2.23" },
|
|
441
|
+
{ name = "sqlite-vec", specifier = ">=0.1.6" },
|
|
440
442
|
]
|
|
441
443
|
|
|
442
444
|
[package.metadata.requires-dev]
|
|
@@ -3107,6 +3109,18 @@ wheels = [
|
|
|
3107
3109
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
|
3108
3110
|
]
|
|
3109
3111
|
|
|
3112
|
+
[[package]]
|
|
3113
|
+
name = "sqlite-vec"
|
|
3114
|
+
version = "0.1.9"
|
|
3115
|
+
source = { registry = "https://pypi.org/simple" }
|
|
3116
|
+
wheels = [
|
|
3117
|
+
{ url = "https://files.pythonhosted.org/packages/68/85/9fad0045d8e7c8df3e0fa5a56c630e8e15ad6e5ca2e6106fceb666aa6638/sqlite_vec-0.1.9-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:1b62a7f0a060d9475575d4e599bbf94a13d85af896bc1ce86ee80d1b5b48e5fb", size = 131171, upload-time = "2026-03-31T08:02:31.717Z" },
|
|
3118
|
+
{ url = "https://files.pythonhosted.org/packages/a4/3d/3677e0cd2f92e5ebc43cd29fbf565b75582bff1ccfa0b8327c7508e1084f/sqlite_vec-0.1.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d52e30513bae4cc9778ddbf6145610434081be4c3afe57cd877893bad9f6b6c", size = 165434, upload-time = "2026-03-31T08:02:32.712Z" },
|
|
3119
|
+
{ url = "https://files.pythonhosted.org/packages/00/d4/f2b936d3bdc38eadcbd2a87875815db36430fab0363182ba5d12cd8e0b51/sqlite_vec-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e921e592f24a5f9a18f590b6ddd530eb637e2d474e3b1972f9bbeb773aa3cb9", size = 160076, upload-time = "2026-03-31T08:02:33.796Z" },
|
|
3120
|
+
{ url = "https://files.pythonhosted.org/packages/6f/ad/6afd073b0f817b3e03f9e37ad626ae341805891f23c74b5292818f49ac63/sqlite_vec-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:1515727990b49e79bcaf75fdee2ffc7d461f8b66905013231251f1c8938e7786", size = 163388, upload-time = "2026-03-31T08:02:34.888Z" },
|
|
3121
|
+
{ url = "https://files.pythonhosted.org/packages/42/89/81b2907cda14e566b9bf215e2ad82fc9b349edf07d2010756ffdb902f328/sqlite_vec-0.1.9-py3-none-win_amd64.whl", hash = "sha256:4a28dc12fa4b53d7b1dced22da2488fade444e96b5d16fd2d698cd670675cf32", size = 292804, upload-time = "2026-03-31T08:02:36.035Z" },
|
|
3122
|
+
]
|
|
3123
|
+
|
|
3110
3124
|
[[package]]
|
|
3111
3125
|
name = "sseclient-py"
|
|
3112
3126
|
version = "1.9.0"
|
|
@@ -106,6 +106,34 @@ append_env_value() {
|
|
|
106
106
|
chmod 600 "$REFLEXIO_ENV"
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
append_env_raw() {
|
|
110
|
+
local key value
|
|
111
|
+
key="$1"
|
|
112
|
+
value="$2"
|
|
113
|
+
mkdir -p "$(dirname "$REFLEXIO_ENV")"
|
|
114
|
+
touch "$REFLEXIO_ENV"
|
|
115
|
+
chmod 600 "$REFLEXIO_ENV"
|
|
116
|
+
printf '%s=%s\n' "$key" "$value" >> "$REFLEXIO_ENV"
|
|
117
|
+
chmod 600 "$REFLEXIO_ENV"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
ensure_local_env_defaults() {
|
|
121
|
+
mkdir -p "$(dirname "$REFLEXIO_ENV")"
|
|
122
|
+
touch "$REFLEXIO_ENV"
|
|
123
|
+
chmod 600 "$REFLEXIO_ENV"
|
|
124
|
+
if ! get_env_value CLAUDE_SMART_USE_LOCAL_CLI >/dev/null 2>&1; then
|
|
125
|
+
printf '# Route reflexio generation through the local Claude Code CLI\n' >> "$REFLEXIO_ENV"
|
|
126
|
+
append_env_raw CLAUDE_SMART_USE_LOCAL_CLI "1"
|
|
127
|
+
fi
|
|
128
|
+
if ! get_env_value CLAUDE_SMART_USE_LOCAL_EMBEDDING >/dev/null 2>&1; then
|
|
129
|
+
printf '# Use the in-process ONNX embedder (chromadb) - no API key for semantic search\n' >> "$REFLEXIO_ENV"
|
|
130
|
+
append_env_raw CLAUDE_SMART_USE_LOCAL_EMBEDDING "1"
|
|
131
|
+
fi
|
|
132
|
+
if ! get_env_value CLAUDE_SMART_READ_ONLY >/dev/null 2>&1; then
|
|
133
|
+
append_env_value CLAUDE_SMART_READ_ONLY "0"
|
|
134
|
+
fi
|
|
135
|
+
}
|
|
136
|
+
|
|
109
137
|
mask_secret() {
|
|
110
138
|
local value len suffix
|
|
111
139
|
value="$1"
|
|
@@ -283,11 +311,11 @@ main() {
|
|
|
283
311
|
|
|
284
312
|
if [ "$mode" = "local" ]; then
|
|
285
313
|
if [ -f "$REFLEXIO_ENV" ]; then
|
|
286
|
-
remove_env_keys
|
|
314
|
+
remove_env_keys REFLEXIO_API_KEY REFLEXIO_URL REFLEXIO_USER_ID
|
|
287
315
|
log "removed managed Reflexio settings from $REFLEXIO_ENV"
|
|
288
|
-
else
|
|
289
|
-
log "local mode selected; no $REFLEXIO_ENV exists, so no env file was created"
|
|
290
316
|
fi
|
|
317
|
+
ensure_local_env_defaults
|
|
318
|
+
log "configured local Reflexio defaults in $REFLEXIO_ENV"
|
|
291
319
|
install_for_host "$host"
|
|
292
320
|
return 0
|
|
293
321
|
fi
|