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 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.39-green.svg" alt="Version">
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">
@@ -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 (e.g. the SessionStart install hook at index 0) without
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "Self-improving Claude Code plugin — learns from corrections via reflexio",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "Self-improving Claude Code plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -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",
@@ -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": [
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claude-smart"
3
- version = "0.2.39"
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; running installer"
205
- CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >>"$STATE_DIR/install.log" 2>&1 || true
206
- claude_smart_source_login_path
207
- claude_smart_prepend_astral_bins
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 after installer; skipping"
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
- runInstaller(root, "backend: uv not on PATH");
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 after installer; skipping");
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
- runInstaller(root, "dashboard: npm not on PATH");
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 after installer; skipping");
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
- if (event === "session-start") {
516
- runInstaller(root, "hook: uv not on PATH");
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 after installer; skipping");
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 || "osc8",
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; running installer" >>"$LOG_FILE"
194
- CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >>"$STATE_DIR/install.log" 2>&1 || true
195
- claude_smart_source_login_path
196
- claude_smart_prepend_node_bins
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 after installer; dashboard cannot start"
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="osc8"
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
- if [ "$EVENT" = "session-start" ] && command -v python3 >/dev/null 2>&1; then
63
- python3 - "$FAILURE_MARKER" <<'PY'
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 Setup/SessionStart bootstrap. SessionStart can
88
- # afford to wait because it has the install budget; prompt/tool hooks start
89
- # the same installer detached so normal work is not blocked by first-run
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
- if [ "$EVENT" = "session-start" ]; then
98
- CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >&2
99
- claude_smart_prepend_astral_bins
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
- if [ "$EVENT" = "session-start" ]; then
131
- CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >&2
132
- else
133
- {
134
- printf '%s\n' "[claude-smart] hook: claude_smart.hook is not importable; retrying install in background"
135
- date 2>/dev/null || true
136
- } >>"$STATE_DIR/install.log" 2>&1
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 and dashboard auto-start on session start." >&2
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 from ``~/.reflexio/.env`` without writing local defaults.
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("error: bash is required to run claude-smart service scripts\n")
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 = _remote_reflexio_page_url(kind)
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(item_id: str, kind: str) -> str:
446
- remote_url = _remote_reflexio_page_url(kind)
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.39"
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