claude-smart 0.2.35 → 0.2.37

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.35-green.svg" alt="Version">
16
+ <img src="https://img.shields.io/badge/version-0.2.37-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">
@@ -5,7 +5,9 @@
5
5
  * plugin. For Codex it copies the bundled local marketplace, registers it,
6
6
  * and enables plugin hooks. Both paths seed ~/.reflexio/.env with the two
7
7
  * local-provider flags so reflexio can route generation through local tools
8
- * with no API key.
8
+ * with no API key. Managed/read-only/global setup is handled by
9
+ * `npx claude-smart setup`, which writes ~/.reflexio/.env before running this
10
+ * installer.
9
11
  *
10
12
  * Keep this file dependency-free — it runs via `npx` with no install step.
11
13
  */
@@ -14,8 +16,6 @@
14
16
  const { execSync, spawn, spawnSync } = require("child_process");
15
17
  const crypto = require("crypto");
16
18
  const {
17
- appendFileSync,
18
- chmodSync,
19
19
  cpSync,
20
20
  existsSync,
21
21
  lstatSync,
@@ -39,8 +39,9 @@ const CODEX_MARKETPLACE_DISPLAY_NAME = "ReflexioAI";
39
39
  const CODEX_PLUGIN_ID = `claude-smart@${CODEX_MARKETPLACE_NAME}`;
40
40
  const REFLEXIO_ENV_PATH = join(homedir(), ".reflexio", ".env");
41
41
  const MANAGED_REFLEXIO_URL = "https://www.reflexio.ai/";
42
- const REFLEXIO_USER_ID_ENV = "REFLEXIO_USER_ID";
43
42
  const MANAGED_SETUP_ENV = "CLAUDE_SMART_MANAGED_SETUP";
43
+ const CLAUDE_SMART_READ_ONLY_ENV = "CLAUDE_SMART_READ_ONLY";
44
+ const REFLEXIO_USER_ID_ENV = "REFLEXIO_USER_ID";
44
45
  const REFLEXIO_DIR = join(homedir(), ".reflexio");
45
46
  const CLAUDE_SMART_STATE_DIR = join(homedir(), ".claude-smart");
46
47
  const CODEX_CONFIG_PATH = join(homedir(), ".codex", "config.toml");
@@ -196,28 +197,6 @@ function runCodex(args) {
196
197
  });
197
198
  }
198
199
 
199
- function seedReflexioEnv() {
200
- mkdirSync(dirname(REFLEXIO_ENV_PATH), { recursive: true });
201
- const existing = existsSync(REFLEXIO_ENV_PATH)
202
- ? readFileSync(REFLEXIO_ENV_PATH, "utf8")
203
- : "";
204
- const flags = [
205
- "CLAUDE_SMART_USE_LOCAL_CLI",
206
- "CLAUDE_SMART_USE_LOCAL_EMBEDDING",
207
- ];
208
- const missing = flags.filter((f) => !new RegExp(`^${f}=`, "m").test(existing));
209
- if (missing.length === 0) return [];
210
- const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
211
- const body = missing.map((f) => `${f}=1`).join("\n") + "\n";
212
- appendFileSync(REFLEXIO_ENV_PATH, prefix + body);
213
- chmodSync(REFLEXIO_ENV_PATH, 0o600);
214
- return missing;
215
- }
216
-
217
- function escapeEnvValue(value) {
218
- return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
219
- }
220
-
221
200
  function parseEnvLine(line) {
222
201
  let trimmed = String(line || "").trim();
223
202
  if (!trimmed || trimmed.startsWith("#")) return null;
@@ -226,55 +205,15 @@ function parseEnvLine(line) {
226
205
  if (eq < 0) return null;
227
206
  const key = trimmed.slice(0, eq).trim();
228
207
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return null;
229
- return { key };
230
- }
231
-
232
- function setReflexioEnvVars(values) {
233
- mkdirSync(dirname(REFLEXIO_ENV_PATH), { recursive: true });
234
- const existing = existsSync(REFLEXIO_ENV_PATH)
235
- ? readFileSync(REFLEXIO_ENV_PATH, "utf8")
236
- : "";
237
- const lines = existing ? existing.split(/\r?\n/) : [];
238
- const seen = new Set();
239
- const out = [];
240
- for (const line of lines) {
241
- const parsed = parseEnvLine(line);
242
- if (parsed && Object.prototype.hasOwnProperty.call(values, parsed.key)) {
243
- out.push(`${parsed.key}="${escapeEnvValue(values[parsed.key])}"`);
244
- seen.add(parsed.key);
245
- } else {
246
- out.push(line);
247
- }
248
- }
249
- const added = [];
250
- for (const [key, value] of Object.entries(values)) {
251
- if (seen.has(key)) continue;
252
- out.push(`${key}="${escapeEnvValue(value)}"`);
253
- added.push(key);
254
- }
255
- const content = out.join("\n").replace(/\n*$/, "\n");
256
- writeFileSync(REFLEXIO_ENV_PATH, content);
257
- chmodSync(REFLEXIO_ENV_PATH, 0o600);
258
- return added;
259
- }
260
-
261
- function readReflexioEnvValue(key) {
262
- const existing = existsSync(REFLEXIO_ENV_PATH)
263
- ? readFileSync(REFLEXIO_ENV_PATH, "utf8")
264
- : "";
265
- for (const line of existing.split(/\r?\n/)) {
266
- const parsed = parseEnvLine(line);
267
- if (!parsed || parsed.key !== key) continue;
268
- let value = line.slice(line.indexOf("=") + 1).trim();
269
- if (
270
- (value.startsWith('"') && value.endsWith('"')) ||
271
- (value.startsWith("'") && value.endsWith("'"))
272
- ) {
273
- value = value.slice(1, -1);
274
- }
275
- return value;
208
+ let value = trimmed.slice(eq + 1).trim();
209
+ if (
210
+ value.length >= 2 &&
211
+ ((value[0] === '"' && value[value.length - 1] === '"') ||
212
+ (value[0] === "'" && value[value.length - 1] === "'"))
213
+ ) {
214
+ value = value.slice(1, -1);
276
215
  }
277
- return "";
216
+ return { key, value };
278
217
  }
279
218
 
280
219
  function maskSecret(value) {
@@ -284,31 +223,47 @@ function maskSecret(value) {
284
223
  return `${prefix}****${value.slice(-4)}`;
285
224
  }
286
225
 
287
- function configureReflexioSetup(args) {
288
- const apiKey = parseOptionalArg(args, "--api-key").trim();
226
+ function loadReflexioSetupEnv() {
227
+ let readOnlyValue = "";
228
+ let fileApiKey = "";
229
+ let fileUrl = "";
230
+ if (existsSync(REFLEXIO_ENV_PATH)) {
231
+ const text = readFileSync(REFLEXIO_ENV_PATH, "utf8");
232
+ for (const line of text.split(/\r?\n/)) {
233
+ const parsed = parseEnvLine(line);
234
+ if (!parsed) continue;
235
+ if (parsed.key === "REFLEXIO_API_KEY") {
236
+ fileApiKey = parsed.value;
237
+ } else if (parsed.key === "REFLEXIO_URL") {
238
+ fileUrl = parsed.value;
239
+ } else if (parsed.key === REFLEXIO_USER_ID_ENV) {
240
+ process.env[parsed.key] = parsed.value;
241
+ } else if (parsed.key === CLAUDE_SMART_READ_ONLY_ENV) {
242
+ readOnlyValue = parsed.value;
243
+ }
244
+ }
245
+ }
246
+ const apiKey = (fileApiKey || process.env.REFLEXIO_API_KEY || "").trim();
289
247
  if (apiKey) {
290
- const reflexioUrl = parseOptionalArg(args, "--reflexio-url") || MANAGED_REFLEXIO_URL;
291
- const userId = readReflexioEnvValue(REFLEXIO_USER_ID_ENV) || crypto.randomUUID();
292
- const added = setReflexioEnvVars({
293
- REFLEXIO_URL: reflexioUrl,
294
- REFLEXIO_API_KEY: apiKey,
295
- [REFLEXIO_USER_ID_ENV]: userId,
296
- });
297
- const changed = added.length > 0 ? added.join(", ") : "managed Reflexio settings";
298
- process.stdout.write(
299
- `Configured ${REFLEXIO_ENV_PATH} for managed Reflexio (${changed}; API key ${maskSecret(apiKey)}).\n`,
300
- );
301
- process.env.REFLEXIO_URL = reflexioUrl;
302
248
  process.env.REFLEXIO_API_KEY = apiKey;
303
- process.env[REFLEXIO_USER_ID_ENV] = userId;
249
+ process.env.REFLEXIO_URL = (fileUrl || process.env.REFLEXIO_URL || MANAGED_REFLEXIO_URL).trim();
304
250
  process.env[MANAGED_SETUP_ENV] = "1";
305
- return;
251
+ process.stdout.write(
252
+ `Using managed Reflexio at ${process.env.REFLEXIO_URL} (API key ${maskSecret(apiKey)}).\n`,
253
+ );
254
+ } else {
255
+ delete process.env.REFLEXIO_URL;
256
+ delete process.env.REFLEXIO_API_KEY;
257
+ delete process.env[MANAGED_SETUP_ENV];
306
258
  }
259
+ const readOnly = ["1", "true", "yes", "on"].includes(
260
+ String(readOnlyValue).trim().toLowerCase(),
261
+ );
262
+ return { readOnly };
263
+ }
307
264
 
308
- const added = seedReflexioEnv();
309
- if (added.length > 0) {
310
- process.stdout.write(`Seeded ${REFLEXIO_ENV_PATH} with ${added.join(", ")}.\n`);
311
- }
265
+ function configureReflexioSetup() {
266
+ return loadReflexioSetupEnv();
312
267
  }
313
268
 
314
269
  function findClaudeCodePluginRoot() {
@@ -752,6 +707,50 @@ function quoteCommandPart(part) {
752
707
  return `"${String(part).replace(/"/g, '\\"')}"`;
753
708
  }
754
709
 
710
+ function commandIsPublishHook(command) {
711
+ if (typeof command !== "string") return false;
712
+ return (
713
+ /hook_entry\.sh\b[\s"']+(?:codex|claude-code)[\s"']+(?:stop|session-end)\b/.test(command) ||
714
+ /codex-hook\.js"?(?:\s+"?hook"?){1}\s+"?(?:stop|session-end)"?/.test(command)
715
+ );
716
+ }
717
+
718
+ function prunePublishHooksForReadOnly(pluginRoot) {
719
+ for (const hookFile of ["hooks.json", "codex-hooks.json"]) {
720
+ const hookPath = join(pluginRoot, "hooks", hookFile);
721
+ if (!existsSync(hookPath)) continue;
722
+ const parsed = JSON.parse(readFileSync(hookPath, "utf8"));
723
+ const hooksByEvent = parsed.hooks || {};
724
+ for (const event of Object.keys(hooksByEvent)) {
725
+ const blocks = [];
726
+ for (const block of hooksByEvent[event] || []) {
727
+ const keptHooks = (block.hooks || []).filter(
728
+ (hook) => !commandIsPublishHook(hook && hook.command),
729
+ );
730
+ if (keptHooks.length > 0) blocks.push({ ...block, hooks: keptHooks });
731
+ }
732
+ if (blocks.length > 0) {
733
+ hooksByEvent[event] = blocks;
734
+ } else {
735
+ delete hooksByEvent[event];
736
+ }
737
+ }
738
+ writeFileSync(hookPath, JSON.stringify(parsed, null, 2) + "\n");
739
+ }
740
+ }
741
+
742
+ function restorePublishHooksFromSource(pluginRoot) {
743
+ const sourceHooksDir = join(PACKAGE_ROOT, "plugin", "hooks");
744
+ const targetHooksDir = join(pluginRoot, "hooks");
745
+ for (const hookFile of ["hooks.json", "codex-hooks.json"]) {
746
+ const sourcePath = join(sourceHooksDir, hookFile);
747
+ const targetPath = join(targetHooksDir, hookFile);
748
+ if (!existsSync(sourcePath) || !existsSync(targetPath)) continue;
749
+ if (sourcePath === targetPath) continue;
750
+ cpSync(sourcePath, targetPath, { force: true });
751
+ }
752
+ }
753
+
755
754
  function patchCodexHooksForNode(pluginRoot, nodePath) {
756
755
  const hookPath = join(pluginRoot, "hooks", "codex-hooks.json");
757
756
  const parsed = JSON.parse(readFileSync(hookPath, "utf8"));
@@ -796,11 +795,12 @@ function ensurePluginRoot(pluginRoot) {
796
795
  }
797
796
  }
798
797
 
799
- async function bootstrapPluginRuntime(pluginRoot) {
798
+ async function bootstrapPluginRuntime(pluginRoot, options = {}) {
800
799
  assertSupportedRuntimePlatform();
801
800
  process.stdout.write("Preparing claude-smart runtime for hooks...\n");
802
801
  const nodeRuntime = await ensurePrivateNode();
803
802
  patchCodexHooksForNode(pluginRoot, nodeRuntime.node);
803
+ if (options.readOnly) prunePublishHooksForReadOnly(pluginRoot);
804
804
  ensurePluginRoot(pluginRoot);
805
805
  const uv = await ensureUv();
806
806
  const env = runtimeEnv([dirname(uv), ...privateNodeBinDirs()]);
@@ -849,16 +849,14 @@ function printHelp() {
849
849
  " npx claude-smart install Install the plugin into Claude Code",
850
850
  " npx claude-smart install --host codex Register the plugin marketplace for Codex",
851
851
  " npx claude-smart install --source <owner/repo> Override the marketplace source",
852
- " npx claude-smart install --api-key <key> Use managed Reflexio service",
852
+ " npx claude-smart setup Configure managed/read-only/global setup",
853
853
  " npx claude-smart uninstall --host codex Remove the Codex marketplace registration",
854
854
  " npx claude-smart --help Show this help",
855
855
  "",
856
856
  "Claude Code install:",
857
857
  " 1. claude plugin marketplace add <source>",
858
858
  ` 2. claude plugin install ${PLUGIN_SPEC}`,
859
- " 3. Appends CLAUDE_SMART_USE_LOCAL_CLI=1 and CLAUDE_SMART_USE_LOCAL_EMBEDDING=1",
860
- " to ~/.reflexio/.env (idempotent).",
861
- " Passing --api-key writes REFLEXIO_URL, REFLEXIO_API_KEY, and REFLEXIO_USER_ID instead.",
859
+ " 3. Reads ~/.reflexio/.env when managed/read-only setup was configured.",
862
860
  "",
863
861
  "Codex install:",
864
862
  ` 1. Copies the bundled marketplace to ${CODEX_MARKETPLACE_DIR}`,
@@ -871,7 +869,7 @@ function printHelp() {
871
869
  "",
872
870
  "Update:",
873
871
  " npx claude-smart update Update to the latest version",
874
- " npx claude-smart update --api-key <key> Update and configure managed Reflexio",
872
+ " npx claude-smart setup Configure managed/read-only/global setup",
875
873
  "",
876
874
  "Uninstall:",
877
875
  " npx claude-smart uninstall Remove the plugin from Claude Code",
@@ -891,17 +889,6 @@ function parseSource(args) {
891
889
  return value;
892
890
  }
893
891
 
894
- function parseOptionalArg(args, flag) {
895
- const idx = args.indexOf(flag);
896
- if (idx === -1) return "";
897
- const value = args[idx + 1];
898
- if (!value) {
899
- process.stderr.write(`error: ${flag} requires a value\n`);
900
- process.exit(1);
901
- }
902
- return value;
903
- }
904
-
905
892
  function parseHost(args) {
906
893
  const idx = args.indexOf("--host");
907
894
  if (idx === -1) return "claude-code";
@@ -1298,6 +1285,7 @@ function installCodexPluginCache(pluginRoot) {
1298
1285
  }
1299
1286
 
1300
1287
  async function runUpdate(args) {
1288
+ const setup = configureReflexioSetup();
1301
1289
  if (!hasClaudeCli()) {
1302
1290
  process.stderr.write(
1303
1291
  "error: 'claude' CLI not found on PATH. " +
@@ -1315,7 +1303,14 @@ async function runUpdate(args) {
1315
1303
  }
1316
1304
 
1317
1305
  process.stdout.write("\nclaude-smart updated. Restart Claude Code to apply.\n");
1318
- if (args.includes("--api-key")) configureReflexioSetup(args);
1306
+ const pluginRoot = findClaudeCodePluginRoot();
1307
+ if (pluginRoot) {
1308
+ restorePublishHooksFromSource(pluginRoot);
1309
+ if (setup.readOnly) {
1310
+ prunePublishHooksForReadOnly(pluginRoot);
1311
+ process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
1312
+ }
1313
+ }
1319
1314
  }
1320
1315
 
1321
1316
  async function runUninstall(args) {
@@ -1353,6 +1348,21 @@ async function runUninstall(args) {
1353
1348
  );
1354
1349
  }
1355
1350
 
1351
+ async function runSetup(args) {
1352
+ const bash = resolveCommand(isWindows() ? ["bash.exe", "bash"] : ["bash"]);
1353
+ if (!bash) {
1354
+ process.stderr.write("error: bash is required to run claude-smart setup.\n");
1355
+ process.exit(1);
1356
+ }
1357
+ const script = join(PACKAGE_ROOT, "scripts", "setup-claude-smart.sh");
1358
+ if (!existsSync(script)) {
1359
+ process.stderr.write(`error: setup script not found at ${script}\n`);
1360
+ process.exit(1);
1361
+ }
1362
+ const code = await runChecked(bash, [script, ...args], { cwd: PACKAGE_ROOT });
1363
+ if (code !== 0) process.exit(code);
1364
+ }
1365
+
1356
1366
  async function runInstall(args) {
1357
1367
  if (parseHost(args) === "codex") {
1358
1368
  await runInstallCodex(args);
@@ -1368,7 +1378,8 @@ async function runInstall(args) {
1368
1378
  }
1369
1379
 
1370
1380
  const source = parseSource(args);
1371
- configureReflexioSetup(args);
1381
+ const setup = configureReflexioSetup();
1382
+ const readOnly = setup.readOnly;
1372
1383
 
1373
1384
  const steps = [
1374
1385
  { args: ["plugin", "marketplace", "add", source], label: "Adding marketplace…" },
@@ -1387,6 +1398,11 @@ async function runInstall(args) {
1387
1398
 
1388
1399
  try {
1389
1400
  const pluginRoot = await bootstrapClaudeCodeInstall();
1401
+ restorePublishHooksFromSource(pluginRoot);
1402
+ if (readOnly) {
1403
+ prunePublishHooksForReadOnly(pluginRoot);
1404
+ process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
1405
+ }
1390
1406
  process.stdout.write(`Prepared claude-smart runtime at ${pluginRoot}.\n`);
1391
1407
  if (refreshDashboardService(pluginRoot)) {
1392
1408
  process.stdout.write("Refreshed claude-smart dashboard service.\n");
@@ -1417,9 +1433,13 @@ async function runInstallCodex(args) {
1417
1433
  process.stderr.write("error: 'codex' CLI not found on PATH. Install Codex first.\n");
1418
1434
  process.exit(1);
1419
1435
  }
1420
- configureReflexioSetup(args);
1436
+ const setup = configureReflexioSetup();
1437
+ const readOnly = setup.readOnly;
1421
1438
 
1422
1439
  const marketplaceRoot = copyCodexMarketplace();
1440
+ if (readOnly) {
1441
+ prunePublishHooksForReadOnly(codexMarketplacePluginRoot(marketplaceRoot));
1442
+ }
1423
1443
  process.stdout.write(`Prepared Codex marketplace at ${marketplaceRoot}.\n`);
1424
1444
 
1425
1445
  let code = await runCodex(["plugin", "marketplace", "add", marketplaceRoot]);
@@ -1460,7 +1480,10 @@ async function runInstallCodex(args) {
1460
1480
  try {
1461
1481
  cacheDir = installCodexPluginCache(codexMarketplacePluginRoot(marketplaceRoot));
1462
1482
  process.stdout.write(`Installed Codex plugin cache at ${cacheDir}.\n`);
1463
- await bootstrapPluginRuntime(cacheDir);
1483
+ await bootstrapPluginRuntime(cacheDir, { readOnly });
1484
+ if (readOnly) {
1485
+ process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
1486
+ }
1464
1487
  if (refreshDashboardService(cacheDir)) {
1465
1488
  process.stdout.write("Refreshed claude-smart dashboard service.\n");
1466
1489
  }
@@ -1553,6 +1576,11 @@ async function main() {
1553
1576
  return;
1554
1577
  }
1555
1578
 
1579
+ if (cmd === "setup") {
1580
+ await runSetup(args.slice(1));
1581
+ return;
1582
+ }
1583
+
1556
1584
  if (cmd === "uninstall") {
1557
1585
  await runUninstall(args.slice(1));
1558
1586
  return;
@@ -1581,4 +1609,6 @@ module.exports = {
1581
1609
  configureReflexioSetup,
1582
1610
  patchCodexHooksForNode,
1583
1611
  platformSupportError,
1612
+ prunePublishHooksForReadOnly,
1613
+ restorePublishHooksFromSource,
1584
1614
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
4
4
  "description": "Self-improving Claude Code plugin — learns from corrections via reflexio",
5
5
  "keywords": [
6
6
  "claude",
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "files": [
29
29
  "bin",
30
+ "scripts/setup-claude-smart.sh",
30
31
  ".agents/plugins/marketplace.json",
31
32
  "plugin",
32
33
  "!plugin/**/__pycache__",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
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.35",
3
+ "version": "0.2.37",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
package/plugin/README.md CHANGED
@@ -16,6 +16,9 @@ under `~/.claude-smart/` when they are missing. If Node.js is already installed,
16
16
  `npx claude-smart install` is equivalent; if uv is already installed,
17
17
  `uvx claude-smart install` is equivalent.
18
18
 
19
+ Add `--read-only` to either install command to skip hooks that publish
20
+ interactions for learning.
21
+
19
22
  Supported vanilla native targets are Apple Silicon macOS 14+ and Windows x64.
20
23
  Intel Mac, macOS 13 or older, and Windows ARM fail early because the local
21
24
  embedding/ML dependency stack does not provide a complete native wheel set.
@@ -206,6 +206,21 @@ export default function ConfigureEnvPage() {
206
206
  />
207
207
  </div>
208
208
 
209
+ <div className="flex items-start justify-between gap-4">
210
+ <div className="min-w-0">
211
+ <Label htmlFor="read-only-mode">CLAUDE_SMART_READ_ONLY</Label>
212
+ <p className="text-xs text-muted-foreground mt-0.5">
213
+ Keep session context available but skip publishing
214
+ interactions to Reflexio.
215
+ </p>
216
+ </div>
217
+ <Switch
218
+ id="read-only-mode"
219
+ checked={!!config.CLAUDE_SMART_READ_ONLY}
220
+ onCheckedChange={(v) => update("CLAUDE_SMART_READ_ONLY", v)}
221
+ />
222
+ </div>
223
+
209
224
  <div className="space-y-2">
210
225
  <Label>CLAUDE_SMART_CLI_PATH</Label>
211
226
  <Input
@@ -11,9 +11,9 @@ import type { ClaudeSmartConfig } from "./types";
11
11
  const KNOWN_KEYS = [
12
12
  "REFLEXIO_URL",
13
13
  "REFLEXIO_API_KEY",
14
- "REFLEXIO_USER_ID",
15
14
  "CLAUDE_SMART_USE_LOCAL_CLI",
16
15
  "CLAUDE_SMART_USE_LOCAL_EMBEDDING",
16
+ "CLAUDE_SMART_READ_ONLY",
17
17
  "CLAUDE_SMART_CLI_PATH",
18
18
  "CLAUDE_SMART_CLI_TIMEOUT",
19
19
  "CLAUDE_SMART_STATE_DIR",
@@ -24,6 +24,7 @@ const KNOWN = new Set<string>(KNOWN_KEYS);
24
24
  const BOOL_KEYS = new Set([
25
25
  "CLAUDE_SMART_USE_LOCAL_CLI",
26
26
  "CLAUDE_SMART_USE_LOCAL_EMBEDDING",
27
+ "CLAUDE_SMART_READ_ONLY",
27
28
  ]);
28
29
 
29
30
  function envPath(): string {
@@ -50,9 +51,9 @@ export async function readConfig(): Promise<ClaudeSmartConfig> {
50
51
  const defaults: ClaudeSmartConfig = {
51
52
  REFLEXIO_URL: "http://localhost:8071/",
52
53
  REFLEXIO_API_KEY: "",
53
- REFLEXIO_USER_ID: "",
54
54
  CLAUDE_SMART_USE_LOCAL_CLI: false,
55
55
  CLAUDE_SMART_USE_LOCAL_EMBEDDING: false,
56
+ CLAUDE_SMART_READ_ONLY: false,
56
57
  CLAUDE_SMART_CLI_PATH: "",
57
58
  CLAUDE_SMART_CLI_TIMEOUT: "120",
58
59
  CLAUDE_SMART_STATE_DIR: "",
@@ -114,10 +114,10 @@ export interface SessionDetail {
114
114
  export interface ClaudeSmartConfig {
115
115
  REFLEXIO_URL: string;
116
116
  REFLEXIO_API_KEY: string;
117
- REFLEXIO_USER_ID: string;
118
117
  REFLEXIO_API_KEY_SET?: boolean;
119
118
  CLAUDE_SMART_USE_LOCAL_CLI: boolean;
120
119
  CLAUDE_SMART_USE_LOCAL_EMBEDDING: boolean;
120
+ CLAUDE_SMART_READ_ONLY: boolean;
121
121
  CLAUDE_SMART_CLI_PATH: string;
122
122
  CLAUDE_SMART_CLI_TIMEOUT: string;
123
123
  CLAUDE_SMART_STATE_DIR: string;
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claude-smart"
3
- version = "0.2.35"
3
+ version = "0.2.37"
4
4
  description = "Self-improving Claude Code plugin — learns from corrections via reflexio"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -63,9 +63,9 @@ claude_smart_source_reflexio_env() {
63
63
  value="${value#"${value%%[![:space:]]*}"}"
64
64
  value="${value%"${value##*[![:space:]]}"}"
65
65
  case "$key" in
66
- # Reflexio identity: file always wins so a rotated key/url overrides
66
+ # Managed Reflexio config: file always wins so a rotated key/url overrides
67
67
  # whatever a long-lived managed backend or dashboard process inherited.
68
- REFLEXIO_URL|REFLEXIO_API_KEY|REFLEXIO_USER_ID)
68
+ REFLEXIO_URL|REFLEXIO_API_KEY|REFLEXIO_USER_ID|CLAUDE_SMART_READ_ONLY)
69
69
  value="$(claude_smart_env_unquote "$value")"
70
70
  export "$key=$value"
71
71
  ;;
@@ -97,10 +97,10 @@ claude_smart_is_internal_invocation_env() {
97
97
  if [ "${CLAUDE_SMART_INTERNAL:-}" = "1" ]; then
98
98
  return 0
99
99
  fi
100
- if [ -n "${CLAUDE_CODE_ENTRYPOINT:-}" ] && [ "${CLAUDE_CODE_ENTRYPOINT:-}" != "cli" ]; then
101
- return 0
102
- fi
103
- return 1
100
+ case "${CLAUDE_CODE_ENTRYPOINT:-}" in
101
+ ""|"cli"|"claude-desktop") return 1 ;;
102
+ *) return 0 ;;
103
+ esac
104
104
  }
105
105
 
106
106
  claude_smart_emit_continue() {
@@ -184,6 +184,7 @@ function loadReflexioEnv() {
184
184
  "REFLEXIO_URL",
185
185
  "REFLEXIO_API_KEY",
186
186
  "REFLEXIO_USER_ID",
187
+ "CLAUDE_SMART_READ_ONLY",
187
188
  ]);
188
189
  const localConfigKeys = new Set([
189
190
  "CLAUDE_SMART_USE_LOCAL_CLI",
@@ -105,11 +105,6 @@ install_complete() {
105
105
  [ "$(cat "$SUCCESS_MARKER" 2>/dev/null || true)" = "$(install_fingerprint)" ] || return 1
106
106
  command -v uv >/dev/null 2>&1 || return 1
107
107
  [ -d "$PLUGIN_ROOT/.venv" ] || return 1
108
- [ -f "$HOME/.reflexio/.env" ] || return 1
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
113
108
  if [ -d "$PLUGIN_ROOT/dashboard" ]; then
114
109
  [ -d "$PLUGIN_ROOT/dashboard/.next" ] || [ -f "$MARKER_DIR/dashboard-build.pid" ] || [ -f "$(claude_smart_dashboard_unavailable_marker)" ] || return 1
115
110
  fi
@@ -432,8 +427,6 @@ fi
432
427
  # append our two opt-in flags there so `reflexio services start` picks
433
428
  # them up regardless of which directory the user runs it from.
434
429
  REFLEXIO_ENV="$HOME/.reflexio/.env"
435
- mkdir -p "$(dirname "$REFLEXIO_ENV")"
436
- touch "$REFLEXIO_ENV"
437
430
  claude_smart_env_quote() {
438
431
  printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
439
432
  }
@@ -472,42 +465,11 @@ claude_smart_env_upsert() {
472
465
  fi
473
466
  }
474
467
 
475
- claude_smart_generate_uuid() {
476
- if [ -r /proc/sys/kernel/random/uuid ]; then
477
- cat /proc/sys/kernel/random/uuid
478
- elif command -v python3 >/dev/null 2>&1; then
479
- python3 -c 'import uuid; print(uuid.uuid4())'
480
- elif command -v python >/dev/null 2>&1; then
481
- python -c 'import uuid; print(uuid.uuid4())'
482
- else
483
- date +%s%N | sed -E 's/^(.{8})(.{4})(.{4})(.{4})(.{12}).*/\1-\2-\3-\4-\5/'
484
- fi
485
- }
486
-
487
- if [ "${CLAUDE_SMART_MANAGED_SETUP:-}" = "1" ] && [ -n "${REFLEXIO_API_KEY:-}" ]; then
488
- REFLEXIO_URL="${REFLEXIO_URL:-https://www.reflexio.ai/}"
489
- REFLEXIO_USER_ID="${REFLEXIO_USER_ID:-$(claude_smart_env_value REFLEXIO_USER_ID || true)}"
490
- REFLEXIO_USER_ID="${REFLEXIO_USER_ID:-$(claude_smart_generate_uuid)}"
491
- claude_smart_env_upsert REFLEXIO_URL "$REFLEXIO_URL"
492
- claude_smart_env_upsert REFLEXIO_API_KEY "$REFLEXIO_API_KEY"
493
- claude_smart_env_upsert REFLEXIO_USER_ID "$REFLEXIO_USER_ID"
494
- chmod 600 "$REFLEXIO_ENV"
495
- echo "[claude-smart] configured managed Reflexio in $REFLEXIO_ENV" >&2
496
- elif [ -z "${REFLEXIO_API_KEY:-}" ]; then
497
- if ! grep -q '^CLAUDE_SMART_USE_LOCAL_CLI=' "$REFLEXIO_ENV"; then
498
- printf '\n# Route reflexio generation through the local Claude Code CLI\nCLAUDE_SMART_USE_LOCAL_CLI=1\n' >> "$REFLEXIO_ENV"
499
- echo "[claude-smart] appended CLAUDE_SMART_USE_LOCAL_CLI=1 to $REFLEXIO_ENV" >&2
500
- fi
501
- if ! grep -q '^CLAUDE_SMART_USE_LOCAL_EMBEDDING=' "$REFLEXIO_ENV"; then
502
- printf '# Use the in-process ONNX embedder (chromadb) — no API key for semantic search\nCLAUDE_SMART_USE_LOCAL_EMBEDDING=1\n' >> "$REFLEXIO_ENV"
503
- echo "[claude-smart] appended CLAUDE_SMART_USE_LOCAL_EMBEDDING=1 to $REFLEXIO_ENV" >&2
504
- fi
505
- fi
506
468
  # Migrate stale REFLEXIO_URL from reflexio's library default (8081) to the
507
469
  # plugin backend port (8071). Matches the quoted and unquoted forms but
508
470
  # requires paired quotes, so malformed or deliberately different values
509
471
  # (e.g. a remote reflexio URL) are preserved.
510
- if grep -qE '^REFLEXIO_URL=("http://localhost:8081/?"|http://localhost:8081/?)$' "$REFLEXIO_ENV"; then
472
+ if [ -f "$REFLEXIO_ENV" ] && grep -qE '^REFLEXIO_URL=("http://localhost:8081/?"|http://localhost:8081/?)$' "$REFLEXIO_ENV"; then
511
473
  sed -i.bak -E \
512
474
  -e 's|^REFLEXIO_URL="http://localhost:8081(/?)"$|REFLEXIO_URL="http://localhost:8071\1"|' \
513
475
  -e 's|^REFLEXIO_URL=http://localhost:8081(/?)$|REFLEXIO_URL=http://localhost:8071\1|' \