claude-smart 0.2.31 → 0.2.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +2 -2
  2. package/bin/claude-smart.js +222 -20
  3. package/package.json +1 -1
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/.codex-plugin/plugin.json +1 -1
  6. package/plugin/dashboard/app/api/config/route.ts +11 -2
  7. package/plugin/dashboard/app/api/health/route.ts +45 -6
  8. package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +15 -7
  9. package/plugin/dashboard/app/api/rules/applied/route.ts +27 -0
  10. package/plugin/dashboard/app/configure/env/page.tsx +36 -31
  11. package/plugin/dashboard/app/configure/layout.tsx +1 -1
  12. package/plugin/dashboard/app/configure/server/page.tsx +8 -14
  13. package/plugin/dashboard/app/dashboard/page.tsx +311 -115
  14. package/plugin/dashboard/app/globals.css +80 -66
  15. package/plugin/dashboard/app/layout.tsx +13 -10
  16. package/plugin/dashboard/app/preferences/[id]/page.tsx +21 -32
  17. package/plugin/dashboard/app/preferences/page.tsx +154 -54
  18. package/plugin/dashboard/app/preferences/project/[id]/page.tsx +1 -0
  19. package/plugin/dashboard/app/rules/[id]/page.tsx +51 -0
  20. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +4 -4
  21. package/plugin/dashboard/app/sessions/page.tsx +14 -10
  22. package/plugin/dashboard/app/skills/page.tsx +175 -56
  23. package/plugin/dashboard/app/skills/project/[id]/page.tsx +22 -38
  24. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +20 -37
  25. package/plugin/dashboard/components/common/delete-learning-danger-zone.tsx +166 -0
  26. package/plugin/dashboard/components/common/empty-state.tsx +4 -2
  27. package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
  28. package/plugin/dashboard/components/common/page-header.tsx +5 -3
  29. package/plugin/dashboard/components/common/page-tabs.tsx +4 -4
  30. package/plugin/dashboard/components/common/stat-card.tsx +9 -5
  31. package/plugin/dashboard/components/layout/sidebar.tsx +24 -9
  32. package/plugin/dashboard/components/layout/top-bar.tsx +37 -25
  33. package/plugin/dashboard/components/ui/input.tsx +1 -0
  34. package/plugin/dashboard/hooks/use-settings.tsx +30 -61
  35. package/plugin/dashboard/hooks/use-stall-state.ts +5 -9
  36. package/plugin/dashboard/lib/config-file.ts +4 -0
  37. package/plugin/dashboard/lib/reflexio-client.ts +23 -48
  38. package/plugin/dashboard/lib/session-reader.ts +222 -6
  39. package/plugin/dashboard/lib/types.ts +21 -1
  40. package/plugin/dashboard/package-lock.json +70 -95
  41. package/plugin/dashboard/package.json +5 -2
  42. package/plugin/pyproject.toml +1 -1
  43. package/plugin/scripts/_lib.sh +61 -0
  44. package/plugin/scripts/backend-service.sh +13 -2
  45. package/plugin/scripts/cli.sh +1 -0
  46. package/plugin/scripts/codex-hook.js +101 -3
  47. package/plugin/scripts/dashboard-service.sh +95 -19
  48. package/plugin/scripts/hook_entry.sh +13 -7
  49. package/plugin/scripts/smart-install.sh +18 -13
  50. package/plugin/src/claude_smart/cli.py +138 -24
  51. package/plugin/src/claude_smart/context_format.py +244 -6
  52. package/plugin/src/claude_smart/context_inject.py +8 -1
  53. package/plugin/src/claude_smart/cs_cite.py +186 -34
  54. package/plugin/src/claude_smart/env_config.py +103 -0
  55. package/plugin/src/claude_smart/events/stop.py +32 -6
  56. package/plugin/src/claude_smart/ids.py +32 -0
  57. package/plugin/src/claude_smart/internal_call.py +30 -0
  58. package/plugin/src/claude_smart/publish.py +8 -3
  59. package/plugin/src/claude_smart/reflexio_adapter.py +106 -29
  60. package/plugin/uv.lock +1 -1
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.31-green.svg" alt="Version">
16
+ <img src="https://img.shields.io/badge/version-0.2.33-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">
@@ -120,7 +120,7 @@ To uninstall:
120
120
  npx claude-smart uninstall --host codex
121
121
  ```
122
122
 
123
- Restart Codex after uninstalling. Learned data under `~/.reflexio/` and `~/.claude-smart/` is preserved and shared with Claude Code, so you can switch between hosts without losing skills or preferences.
123
+ Restart Codex after uninstalling. The uninstaller stops local claude-smart services and removes plugin/cache/config state; learned data under `~/.reflexio/` and `~/.claude-smart/` is preserved and shared with Claude Code, so you can switch between hosts without losing skills or preferences.
124
124
 
125
125
  Developing the plugin itself? See [DEVELOPER.md](./DEVELOPER.md#developing-locally) for what the installer does, manual toggles via `/plugins`, and clone-based development.
126
126
 
@@ -11,10 +11,11 @@
11
11
  */
12
12
  "use strict";
13
13
 
14
- const { execSync, spawn } = require("child_process");
14
+ const { execSync, spawn, spawnSync } = require("child_process");
15
15
  const crypto = require("crypto");
16
16
  const {
17
17
  appendFileSync,
18
+ chmodSync,
18
19
  cpSync,
19
20
  existsSync,
20
21
  lstatSync,
@@ -37,6 +38,8 @@ const CODEX_MARKETPLACE_NAME = "reflexioai";
37
38
  const CODEX_MARKETPLACE_DISPLAY_NAME = "ReflexioAI";
38
39
  const CODEX_PLUGIN_ID = `claude-smart@${CODEX_MARKETPLACE_NAME}`;
39
40
  const REFLEXIO_ENV_PATH = join(homedir(), ".reflexio", ".env");
41
+ const MANAGED_REFLEXIO_URL = "https://www.reflexio.ai/";
42
+ const REFLEXIO_USER_ID_ENV = "REFLEXIO_USER_ID";
40
43
  const REFLEXIO_DIR = join(homedir(), ".reflexio");
41
44
  const CLAUDE_SMART_STATE_DIR = join(homedir(), ".claude-smart");
42
45
  const CODEX_CONFIG_PATH = join(homedir(), ".codex", "config.toml");
@@ -48,7 +51,7 @@ const CODEX_MARKETPLACE_DIR = join(
48
51
  "marketplaces",
49
52
  CODEX_MARKETPLACE_NAME,
50
53
  );
51
- const CODEX_MARKETPLACE_PLUGIN_PATH = join("plugins", "claude-smart");
54
+ const CODEX_MARKETPLACE_PLUGIN_PATH = "plugin";
52
55
  const CODEX_PLUGIN_CACHE_DIR = join(
53
56
  homedir(),
54
57
  ".codex",
@@ -57,6 +60,14 @@ const CODEX_PLUGIN_CACHE_DIR = join(
57
60
  CODEX_MARKETPLACE_NAME,
58
61
  "claude-smart",
59
62
  );
63
+ const LOCAL_DATA_NOTICE = [
64
+ "Local data was kept so reinstalling claude-smart can reuse your learned rules, sessions, logs, and local Reflexio data.",
65
+ "Kept folders:",
66
+ " ~/.claude-smart",
67
+ " ~/.reflexio",
68
+ "Delete them only if you want a full reset or need to remove local claude-smart data from this machine:",
69
+ " rm -rf ~/.claude-smart ~/.reflexio",
70
+ ];
60
71
  const CODEX_REQUIRED_FILES = [
61
72
  ".agents/plugins/marketplace.json",
62
73
  "plugin/.codex-plugin/plugin.json",
@@ -68,6 +79,7 @@ const CODEX_REQUIRED_FILES = [
68
79
  "plugin/scripts/_codex_env.sh",
69
80
  ];
70
81
  const CODEX_CLI_TIMEOUT_MS = 30_000;
82
+ const PLUGIN_SERVICE_TIMEOUT_MS = 15_000;
71
83
  const COPYTREE_IGNORE_NAMES = new Set([
72
84
  "__pycache__",
73
85
  ".venv",
@@ -197,9 +209,103 @@ function seedReflexioEnv() {
197
209
  const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
198
210
  const body = missing.map((f) => `${f}=1`).join("\n") + "\n";
199
211
  appendFileSync(REFLEXIO_ENV_PATH, prefix + body);
212
+ chmodSync(REFLEXIO_ENV_PATH, 0o600);
200
213
  return missing;
201
214
  }
202
215
 
216
+ function escapeEnvValue(value) {
217
+ return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
218
+ }
219
+
220
+ function parseEnvLine(line) {
221
+ let trimmed = String(line || "").trim();
222
+ if (!trimmed || trimmed.startsWith("#")) return null;
223
+ if (trimmed.startsWith("export ")) trimmed = trimmed.slice("export ".length).trimStart();
224
+ const eq = trimmed.indexOf("=");
225
+ if (eq < 0) return null;
226
+ const key = trimmed.slice(0, eq).trim();
227
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return null;
228
+ return { key };
229
+ }
230
+
231
+ function setReflexioEnvVars(values) {
232
+ mkdirSync(dirname(REFLEXIO_ENV_PATH), { recursive: true });
233
+ const existing = existsSync(REFLEXIO_ENV_PATH)
234
+ ? readFileSync(REFLEXIO_ENV_PATH, "utf8")
235
+ : "";
236
+ const lines = existing ? existing.split(/\r?\n/) : [];
237
+ const seen = new Set();
238
+ const out = [];
239
+ for (const line of lines) {
240
+ const parsed = parseEnvLine(line);
241
+ if (parsed && Object.prototype.hasOwnProperty.call(values, parsed.key)) {
242
+ out.push(`${parsed.key}="${escapeEnvValue(values[parsed.key])}"`);
243
+ seen.add(parsed.key);
244
+ } else {
245
+ out.push(line);
246
+ }
247
+ }
248
+ const added = [];
249
+ for (const [key, value] of Object.entries(values)) {
250
+ if (seen.has(key)) continue;
251
+ out.push(`${key}="${escapeEnvValue(value)}"`);
252
+ added.push(key);
253
+ }
254
+ const content = out.join("\n").replace(/\n*$/, "\n");
255
+ writeFileSync(REFLEXIO_ENV_PATH, content);
256
+ chmodSync(REFLEXIO_ENV_PATH, 0o600);
257
+ return added;
258
+ }
259
+
260
+ function readReflexioEnvValue(key) {
261
+ const existing = existsSync(REFLEXIO_ENV_PATH)
262
+ ? readFileSync(REFLEXIO_ENV_PATH, "utf8")
263
+ : "";
264
+ for (const line of existing.split(/\r?\n/)) {
265
+ const parsed = parseEnvLine(line);
266
+ if (!parsed || parsed.key !== key) continue;
267
+ let value = line.slice(line.indexOf("=") + 1).trim();
268
+ if (
269
+ (value.startsWith('"') && value.endsWith('"')) ||
270
+ (value.startsWith("'") && value.endsWith("'"))
271
+ ) {
272
+ value = value.slice(1, -1);
273
+ }
274
+ return value;
275
+ }
276
+ return "";
277
+ }
278
+
279
+ function maskSecret(value) {
280
+ if (!value) return "";
281
+ if (value.length <= 8) return "*".repeat(value.length);
282
+ const prefix = value.slice(0, 8).includes("-") ? value.slice(0, 5) : value.slice(0, 4);
283
+ return `${prefix}****${value.slice(-4)}`;
284
+ }
285
+
286
+ function configureReflexioSetup(args) {
287
+ const apiKey = parseOptionalArg(args, "--api-key").trim();
288
+ if (apiKey) {
289
+ const reflexioUrl = parseOptionalArg(args, "--reflexio-url") || MANAGED_REFLEXIO_URL;
290
+ const userId = readReflexioEnvValue(REFLEXIO_USER_ID_ENV) || crypto.randomUUID();
291
+ const added = setReflexioEnvVars({
292
+ REFLEXIO_URL: reflexioUrl,
293
+ REFLEXIO_API_KEY: apiKey,
294
+ [REFLEXIO_USER_ID_ENV]: userId,
295
+ });
296
+ const changed = added.length > 0 ? added.join(", ") : "managed Reflexio settings";
297
+ process.stdout.write(
298
+ `Configured ${REFLEXIO_ENV_PATH} for managed Reflexio (${changed}; API key ${maskSecret(apiKey)}).\n`,
299
+ );
300
+ return;
301
+ }
302
+
303
+ const added = seedReflexioEnv();
304
+ if (added.length > 0) {
305
+ process.stdout.write(`Seeded ${REFLEXIO_ENV_PATH} with ${added.join(", ")}.\n`);
306
+ }
307
+ }
308
+
203
309
  function findClaudeCodePluginRoot() {
204
310
  const cacheRoot = join(homedir(), ".claude", "plugins", "cache", CODEX_MARKETPLACE_NAME, "claude-smart");
205
311
  const candidates = [];
@@ -218,6 +324,8 @@ function findClaudeCodePluginRoot() {
218
324
  // Fall through to marketplace/package fallbacks.
219
325
  }
220
326
  candidates.sort((a, b) => {
327
+ const versionCompare = compareSemverLikePathNames(b, a);
328
+ if (versionCompare !== 0) return versionCompare;
221
329
  try {
222
330
  return statSync(b).mtimeMs - statSync(a).mtimeMs;
223
331
  } catch {
@@ -239,6 +347,27 @@ function findClaudeCodePluginRoot() {
239
347
  return null;
240
348
  }
241
349
 
350
+ function semverLikePathName(path) {
351
+ const base = String(path).split(/[\\/]/).pop() || "";
352
+ const match = base.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
353
+ if (!match) return null;
354
+ return match.slice(1).map((part) => Number.parseInt(part, 10));
355
+ }
356
+
357
+ function compareSemverLikePathNames(a, b) {
358
+ const av = semverLikePathName(a);
359
+ const bv = semverLikePathName(b);
360
+ if (av && bv) {
361
+ for (let i = 0; i < 3; i += 1) {
362
+ if (av[i] !== bv[i]) return av[i] - bv[i];
363
+ }
364
+ return 0;
365
+ }
366
+ if (av) return 1;
367
+ if (bv) return -1;
368
+ return 0;
369
+ }
370
+
242
371
  function forcePluginRoot(pluginRoot) {
243
372
  mkdirSync(REFLEXIO_DIR, { recursive: true });
244
373
  const link = join(REFLEXIO_DIR, "plugin-root");
@@ -342,6 +471,45 @@ function runChecked(command, args, options = {}) {
342
471
  });
343
472
  }
344
473
 
474
+ function runPluginService(pluginRoot, scriptName, subcommand) {
475
+ const script = join(pluginRoot, "scripts", scriptName);
476
+ if (!existsSync(script)) return false;
477
+ const bash = resolveCommand(isWindows() ? ["bash.exe", "bash"] : ["bash"]);
478
+ if (!bash) return false;
479
+ const result = spawnSync(bash, [script, subcommand], {
480
+ cwd: pluginRoot,
481
+ env: runtimeEnv(),
482
+ stdio: "ignore",
483
+ windowsHide: true,
484
+ timeout: PLUGIN_SERVICE_TIMEOUT_MS,
485
+ killSignal: "SIGTERM",
486
+ });
487
+ if (result.error || result.signal) {
488
+ const reason = result.error && result.error.code === "ETIMEDOUT"
489
+ ? `timed out after ${PLUGIN_SERVICE_TIMEOUT_MS / 1000}s`
490
+ : result.error
491
+ ? result.error.message
492
+ : `terminated by ${result.signal}`;
493
+ process.stderr.write(
494
+ `warning: ${scriptName} ${subcommand} ${reason}; continuing.\n`,
495
+ );
496
+ return false;
497
+ }
498
+ return result.status === 0;
499
+ }
500
+
501
+ function refreshDashboardService(pluginRoot) {
502
+ // dashboard-service.sh is marker-gated: stop only reaps a listener that
503
+ // identifies as claude-smart, so foreign apps on 3001 are left alone.
504
+ runPluginService(pluginRoot, "dashboard-service.sh", "stop");
505
+ return runPluginService(pluginRoot, "dashboard-service.sh", "start");
506
+ }
507
+
508
+ function stopClaudeSmartServices(pluginRoot) {
509
+ runPluginService(pluginRoot, "dashboard-service.sh", "stop");
510
+ runPluginService(pluginRoot, "backend-service.sh", "stop");
511
+ }
512
+
345
513
  function downloadFile(url, dest) {
346
514
  return new Promise((resolve, reject) => {
347
515
  const request = https.get(url, (response) => {
@@ -676,6 +844,7 @@ function printHelp() {
676
844
  " npx claude-smart install Install the plugin into Claude Code",
677
845
  " npx claude-smart install --host codex Register the plugin marketplace for Codex",
678
846
  " npx claude-smart install --source <owner/repo> Override the marketplace source",
847
+ " npx claude-smart install --api-key <key> Use managed Reflexio service",
679
848
  " npx claude-smart uninstall --host codex Remove the Codex marketplace registration",
680
849
  " npx claude-smart --help Show this help",
681
850
  "",
@@ -684,6 +853,7 @@ function printHelp() {
684
853
  ` 2. claude plugin install ${PLUGIN_SPEC}`,
685
854
  " 3. Appends CLAUDE_SMART_USE_LOCAL_CLI=1 and CLAUDE_SMART_USE_LOCAL_EMBEDDING=1",
686
855
  " to ~/.reflexio/.env (idempotent).",
856
+ " Passing --api-key writes REFLEXIO_URL, REFLEXIO_API_KEY, and REFLEXIO_USER_ID instead.",
687
857
  "",
688
858
  "Codex install:",
689
859
  ` 1. Copies the bundled marketplace to ${CODEX_MARKETPLACE_DIR}`,
@@ -696,6 +866,7 @@ function printHelp() {
696
866
  "",
697
867
  "Update:",
698
868
  " npx claude-smart update Update to the latest version",
869
+ " npx claude-smart update --api-key <key> Update and configure managed Reflexio",
699
870
  "",
700
871
  "Uninstall:",
701
872
  " npx claude-smart uninstall Remove the plugin from Claude Code",
@@ -715,6 +886,17 @@ function parseSource(args) {
715
886
  return value;
716
887
  }
717
888
 
889
+ function parseOptionalArg(args, flag) {
890
+ const idx = args.indexOf(flag);
891
+ if (idx === -1) return "";
892
+ const value = args[idx + 1];
893
+ if (!value) {
894
+ process.stderr.write(`error: ${flag} requires a value\n`);
895
+ process.exit(1);
896
+ }
897
+ return value;
898
+ }
899
+
718
900
  function parseHost(args) {
719
901
  const idx = args.indexOf("--host");
720
902
  if (idx === -1) return "claude-code";
@@ -791,6 +973,21 @@ function copyCodexMarketplace() {
791
973
  return CODEX_MARKETPLACE_DIR;
792
974
  }
793
975
 
976
+ function codexMarketplacePluginRoot(marketplaceRoot) {
977
+ const manifestPath = join(marketplaceRoot, ".agents", "plugins", "marketplace.json");
978
+ const fallback = join(marketplaceRoot, CODEX_MARKETPLACE_PLUGIN_PATH);
979
+ try {
980
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
981
+ const entry = (manifest.plugins || []).find((plugin) => plugin.name === "claude-smart");
982
+ const rawPath = entry && entry.source && entry.source.path;
983
+ if (typeof rawPath !== "string" || !rawPath) return fallback;
984
+ const relPath = rawPath.replace(/^\.\//, "");
985
+ return join(marketplaceRoot, relPath);
986
+ } catch {
987
+ return fallback;
988
+ }
989
+ }
990
+
794
991
  function removeTomlSections(path, { exact, prefixes = [] }) {
795
992
  if (!existsSync(path)) return true;
796
993
  const text = readFileSync(path, "utf8");
@@ -1095,7 +1292,7 @@ function installCodexPluginCache(pluginRoot) {
1095
1292
  return cacheDir;
1096
1293
  }
1097
1294
 
1098
- async function runUpdate() {
1295
+ async function runUpdate(args) {
1099
1296
  if (!hasClaudeCli()) {
1100
1297
  process.stderr.write(
1101
1298
  "error: 'claude' CLI not found on PATH. " +
@@ -1113,6 +1310,7 @@ async function runUpdate() {
1113
1310
  }
1114
1311
 
1115
1312
  process.stdout.write("\nclaude-smart updated. Restart Claude Code to apply.\n");
1313
+ if (args.includes("--api-key")) configureReflexioSetup(args);
1116
1314
  }
1117
1315
 
1118
1316
  async function runUninstall(args) {
@@ -1138,12 +1336,13 @@ async function runUninstall(args) {
1138
1336
  );
1139
1337
  process.exit(code);
1140
1338
  }
1339
+ stopClaudeSmartServices(join(PACKAGE_ROOT, "plugin"));
1141
1340
 
1142
1341
  process.stdout.write(
1143
1342
  [
1144
1343
  "",
1145
1344
  "claude-smart uninstalled. Restart Claude Code to apply.",
1146
- "Local data in ~/.reflexio/ and ~/.claude-smart/ was left in place — remove manually if desired.",
1345
+ ...LOCAL_DATA_NOTICE,
1147
1346
  "",
1148
1347
  ].join("\n"),
1149
1348
  );
@@ -1151,7 +1350,7 @@ async function runUninstall(args) {
1151
1350
 
1152
1351
  async function runInstall(args) {
1153
1352
  if (parseHost(args) === "codex") {
1154
- await runInstallCodex();
1353
+ await runInstallCodex(args);
1155
1354
  return;
1156
1355
  }
1157
1356
 
@@ -1164,6 +1363,8 @@ async function runInstall(args) {
1164
1363
  }
1165
1364
 
1166
1365
  const source = parseSource(args);
1366
+ configureReflexioSetup(args);
1367
+
1167
1368
  const steps = [
1168
1369
  { args: ["plugin", "marketplace", "add", source], label: "Adding marketplace…" },
1169
1370
  { args: ["plugin", "install", PLUGIN_SPEC], label: "Installing claude-smart…" },
@@ -1179,15 +1380,12 @@ async function runInstall(args) {
1179
1380
  }
1180
1381
  }
1181
1382
 
1182
- const added = seedReflexioEnv();
1183
- if (added.length > 0) {
1184
- process.stdout.write(
1185
- `Seeded ${REFLEXIO_ENV_PATH} with ${added.join(", ")}.\n`,
1186
- );
1187
- }
1188
1383
  try {
1189
1384
  const pluginRoot = await bootstrapClaudeCodeInstall();
1190
1385
  process.stdout.write(`Prepared claude-smart runtime at ${pluginRoot}.\n`);
1386
+ if (refreshDashboardService(pluginRoot)) {
1387
+ process.stdout.write("Refreshed claude-smart dashboard service.\n");
1388
+ }
1191
1389
  } catch (err) {
1192
1390
  process.stderr.write(
1193
1391
  `error: claude-smart installed, but dependency bootstrap failed: ${err && err.message ? err.message : err}\n`,
@@ -1209,11 +1407,12 @@ async function runInstall(args) {
1209
1407
  );
1210
1408
  }
1211
1409
 
1212
- async function runInstallCodex() {
1410
+ async function runInstallCodex(args) {
1213
1411
  if (!hasCli("codex")) {
1214
1412
  process.stderr.write("error: 'codex' CLI not found on PATH. Install Codex first.\n");
1215
1413
  process.exit(1);
1216
1414
  }
1415
+ configureReflexioSetup(args);
1217
1416
 
1218
1417
  const marketplaceRoot = copyCodexMarketplace();
1219
1418
  process.stdout.write(`Prepared Codex marketplace at ${marketplaceRoot}.\n`);
@@ -1254,9 +1453,12 @@ async function runInstallCodex() {
1254
1453
  let trustedHookCount = 0;
1255
1454
  let trustError = null;
1256
1455
  try {
1257
- cacheDir = installCodexPluginCache(join(marketplaceRoot, CODEX_MARKETPLACE_PLUGIN_PATH));
1456
+ cacheDir = installCodexPluginCache(codexMarketplacePluginRoot(marketplaceRoot));
1258
1457
  process.stdout.write(`Installed Codex plugin cache at ${cacheDir}.\n`);
1259
1458
  await bootstrapPluginRuntime(cacheDir);
1459
+ if (refreshDashboardService(cacheDir)) {
1460
+ process.stdout.write("Refreshed claude-smart dashboard service.\n");
1461
+ }
1260
1462
  } catch (err) {
1261
1463
  process.stderr.write(
1262
1464
  `error: automatic Codex plugin install failed: ${err && err.message ? err.message : err}\n`,
@@ -1289,11 +1491,6 @@ async function runInstallCodex() {
1289
1491
  process.stdout.write(`Trusted and enabled ${trustedHookCount} claude-smart Codex hooks.\n`);
1290
1492
  }
1291
1493
 
1292
- const added = seedReflexioEnv();
1293
- if (added.length > 0) {
1294
- process.stdout.write(`Seeded ${REFLEXIO_ENV_PATH} with ${added.join(", ")}.\n`);
1295
- }
1296
-
1297
1494
  process.stdout.write(
1298
1495
  [
1299
1496
  "",
@@ -1306,6 +1503,7 @@ async function runInstallCodex() {
1306
1503
  }
1307
1504
 
1308
1505
  async function runUninstallCodex() {
1506
+ stopClaudeSmartServices(join(PACKAGE_ROOT, "plugin"));
1309
1507
  if (!hasCli("codex")) {
1310
1508
  process.stdout.write("Codex CLI not found; skipping marketplace removal.\n");
1311
1509
  cleanupCodexInstallState();
@@ -1324,7 +1522,8 @@ async function runUninstallCodex() {
1324
1522
  [
1325
1523
  "",
1326
1524
  "claude-smart Codex plugin and marketplace state removed. Restart Codex to apply.",
1327
- "Codex's global hook feature flags and local data under ~/.reflexio/ and ~/.claude-smart/ were left in place.",
1525
+ "Codex's global hook feature flags were left in place.",
1526
+ ...LOCAL_DATA_NOTICE,
1328
1527
  "",
1329
1528
  ].join("\n"),
1330
1529
  );
@@ -1345,7 +1544,7 @@ async function main() {
1345
1544
  }
1346
1545
 
1347
1546
  if (cmd === "update") {
1348
- await runUpdate();
1547
+ await runUpdate(args.slice(1));
1349
1548
  return;
1350
1549
  }
1351
1550
 
@@ -1370,8 +1569,11 @@ if (require.main === module) {
1370
1569
  module.exports = {
1371
1570
  assertSupportedRuntimePlatform,
1372
1571
  bootstrapPluginRuntime,
1572
+ codexMarketplacePluginRoot,
1573
+ copyCodexMarketplace,
1373
1574
  ensurePrivateNode,
1374
1575
  ensureUv,
1576
+ configureReflexioSetup,
1375
1577
  patchCodexHooksForNode,
1376
1578
  platformSupportError,
1377
1579
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
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.31",
3
+ "version": "0.2.33",
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.31",
3
+ "version": "0.2.33",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -1,16 +1,25 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { readConfig, writeConfig } from "@/lib/config-file";
3
+ import type { ClaudeSmartConfig } from "@/lib/types";
3
4
 
4
5
  export const dynamic = "force-dynamic";
5
6
 
7
+ function publicConfig(config: ClaudeSmartConfig): ClaudeSmartConfig {
8
+ return {
9
+ ...config,
10
+ REFLEXIO_API_KEY: "",
11
+ REFLEXIO_API_KEY_SET: Boolean(config.REFLEXIO_API_KEY),
12
+ };
13
+ }
14
+
6
15
  export async function GET() {
7
16
  const config = await readConfig();
8
- return NextResponse.json(config);
17
+ return NextResponse.json(publicConfig(config));
9
18
  }
10
19
 
11
20
  export async function PUT(req: Request) {
12
21
  const body = await req.json();
13
22
  await writeConfig(body);
14
23
  const config = await readConfig();
15
- return NextResponse.json(config);
24
+ return NextResponse.json(publicConfig(config));
16
25
  }
@@ -1,10 +1,49 @@
1
- export const dynamic = "force-static";
1
+ import { readFileSync, realpathSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export const dynamic = "force-dynamic";
5
+
6
+ function realpathOrSelf(value: string): string {
7
+ try {
8
+ return realpathSync(value);
9
+ } catch {
10
+ return value;
11
+ }
12
+ }
13
+
14
+ function pluginVersion(pluginRoot: string): string {
15
+ try {
16
+ const manifest = JSON.parse(
17
+ readFileSync(path.join(pluginRoot, ".codex-plugin", "plugin.json"), "utf8"),
18
+ );
19
+ return typeof manifest.version === "string" && manifest.version
20
+ ? manifest.version
21
+ : "unknown";
22
+ } catch {
23
+ return "unknown";
24
+ }
25
+ }
2
26
 
3
27
  export function GET() {
4
- return new Response(JSON.stringify({ service: "claude-smart-dashboard" }), {
5
- headers: {
6
- "content-type": "application/json",
7
- "x-claude-smart-dashboard": "1",
28
+ const dashboardDir = realpathOrSelf(process.cwd());
29
+ const pluginRoot = realpathOrSelf(path.dirname(dashboardDir));
30
+ const version = pluginVersion(pluginRoot);
31
+
32
+ return new Response(
33
+ JSON.stringify({
34
+ service: "claude-smart-dashboard",
35
+ pluginRoot,
36
+ dashboardDir,
37
+ version,
38
+ }),
39
+ {
40
+ headers: {
41
+ "content-type": "application/json",
42
+ "x-claude-smart-dashboard": "1",
43
+ "x-claude-smart-plugin-root": pluginRoot,
44
+ "x-claude-smart-dashboard-dir": dashboardDir,
45
+ "x-claude-smart-version": version,
46
+ },
8
47
  },
9
- });
48
+ );
10
49
  }
@@ -1,16 +1,21 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { originOnly } from "@/lib/reflexio-url";
3
+ import { readConfig } from "@/lib/config-file";
3
4
 
4
5
  export const dynamic = "force-dynamic";
5
6
 
6
7
  const DEFAULT_URL = "http://localhost:8071";
7
8
 
8
- function reflexioBase(req: Request): string {
9
- const header = req.headers.get("x-reflexio-url");
10
- const fromHeader = header ? originOnly(header) : null;
11
- if (fromHeader) return fromHeader;
9
+ async function reflexioConfig(): Promise<{ base: string; apiKey: string }> {
10
+ const config = await readConfig();
11
+ const apiKey = config.REFLEXIO_API_KEY || process.env.REFLEXIO_API_KEY || "";
12
12
  const fromEnv = originOnly(process.env.REFLEXIO_URL ?? "");
13
- return fromEnv ?? DEFAULT_URL;
13
+ const fromConfig = originOnly(config.REFLEXIO_URL ?? "");
14
+ const configuredBase = fromEnv ?? fromConfig;
15
+ return {
16
+ base: configuredBase ?? DEFAULT_URL,
17
+ apiKey: configuredBase ? apiKey : "",
18
+ };
14
19
  }
15
20
 
16
21
  async function proxy(
@@ -20,12 +25,15 @@ async function proxy(
20
25
  const { path } = await context.params;
21
26
  const targetPath = path.join("/");
22
27
  const url = new URL(req.url);
23
- const target = `${reflexioBase(req)}/${targetPath}${url.search}`;
28
+ const { base, apiKey } = await reflexioConfig();
29
+ const target = `${base}/${targetPath}${url.search}`;
24
30
 
25
31
  const headers = new Headers(req.headers);
26
32
  headers.delete("host");
27
- headers.delete("x-reflexio-url");
28
33
  headers.delete("connection");
34
+ headers.delete("authorization");
35
+ headers.set("user-agent", "claude-smart");
36
+ if (apiKey) headers.set("authorization", `Bearer ${apiKey}`);
29
37
 
30
38
  const init: RequestInit = {
31
39
  method: req.method,
@@ -0,0 +1,27 @@
1
+ import { NextResponse } from "next/server";
2
+ import { listAppliedRules } from "@/lib/session-reader";
3
+
4
+ export const dynamic = "force-dynamic";
5
+
6
+ const MAX_DAYS_BACK = 365;
7
+ const MAX_LIMIT = 500;
8
+
9
+ function positiveInt(value: string | null, fallback: number): number {
10
+ if (!value) return fallback;
11
+ const parsed = Number.parseInt(value, 10);
12
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
13
+ }
14
+
15
+ export async function GET(req: Request) {
16
+ const url = new URL(req.url);
17
+ const daysBack = Math.min(
18
+ positiveInt(url.searchParams.get("daysBack"), 30),
19
+ MAX_DAYS_BACK,
20
+ );
21
+ const limit = Math.min(
22
+ positiveInt(url.searchParams.get("limit"), 20),
23
+ MAX_LIMIT,
24
+ );
25
+ const stats = await listAppliedRules({ daysBack, limit });
26
+ return NextResponse.json({ success: true, stats });
27
+ }