claude-smart 0.2.31 → 0.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/claude-smart.js +222 -20
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/dashboard/app/api/config/route.ts +11 -2
- package/plugin/dashboard/app/api/health/route.ts +45 -6
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +15 -7
- package/plugin/dashboard/app/api/rules/applied/route.ts +27 -0
- package/plugin/dashboard/app/configure/env/page.tsx +36 -31
- package/plugin/dashboard/app/configure/layout.tsx +1 -1
- package/plugin/dashboard/app/configure/server/page.tsx +8 -14
- package/plugin/dashboard/app/dashboard/page.tsx +311 -115
- package/plugin/dashboard/app/globals.css +80 -66
- package/plugin/dashboard/app/layout.tsx +13 -10
- package/plugin/dashboard/app/preferences/[id]/page.tsx +21 -32
- package/plugin/dashboard/app/preferences/page.tsx +154 -54
- package/plugin/dashboard/app/preferences/project/[id]/page.tsx +1 -0
- package/plugin/dashboard/app/rules/[id]/page.tsx +51 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +4 -4
- package/plugin/dashboard/app/sessions/page.tsx +14 -10
- package/plugin/dashboard/app/skills/page.tsx +175 -56
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +22 -38
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +20 -37
- package/plugin/dashboard/components/common/delete-learning-danger-zone.tsx +166 -0
- package/plugin/dashboard/components/common/empty-state.tsx +4 -2
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +5 -3
- package/plugin/dashboard/components/common/page-tabs.tsx +4 -4
- package/plugin/dashboard/components/common/stat-card.tsx +9 -5
- package/plugin/dashboard/components/layout/sidebar.tsx +24 -9
- package/plugin/dashboard/components/layout/top-bar.tsx +37 -25
- package/plugin/dashboard/components/ui/input.tsx +1 -0
- package/plugin/dashboard/hooks/use-settings.tsx +30 -61
- package/plugin/dashboard/hooks/use-stall-state.ts +5 -9
- package/plugin/dashboard/lib/config-file.ts +4 -0
- package/plugin/dashboard/lib/reflexio-client.ts +23 -48
- package/plugin/dashboard/lib/session-reader.ts +222 -6
- package/plugin/dashboard/lib/types.ts +21 -1
- package/plugin/dashboard/package-lock.json +70 -95
- package/plugin/dashboard/package.json +5 -2
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +61 -0
- package/plugin/scripts/backend-service.sh +13 -2
- package/plugin/scripts/cli.sh +1 -0
- package/plugin/scripts/codex-hook.js +101 -3
- package/plugin/scripts/dashboard-service.sh +95 -19
- package/plugin/scripts/hook_entry.sh +13 -7
- package/plugin/scripts/smart-install.sh +18 -13
- package/plugin/src/claude_smart/cli.py +138 -24
- package/plugin/src/claude_smart/context_format.py +244 -6
- package/plugin/src/claude_smart/context_inject.py +8 -1
- package/plugin/src/claude_smart/cs_cite.py +186 -34
- package/plugin/src/claude_smart/env_config.py +103 -0
- package/plugin/src/claude_smart/events/stop.py +32 -6
- package/plugin/src/claude_smart/ids.py +32 -0
- package/plugin/src/claude_smart/internal_call.py +30 -0
- package/plugin/src/claude_smart/publish.py +8 -3
- package/plugin/src/claude_smart/reflexio_adapter.py +106 -29
- package/plugin/uv.lock +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
|
|
14
14
|
</a>
|
|
15
15
|
<a href="plugin/pyproject.toml">
|
|
16
|
-
<img src="https://img.shields.io/badge/version-0.2.
|
|
16
|
+
<img src="https://img.shields.io/badge/version-0.2.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.
|
|
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
|
|
package/bin/claude-smart.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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(
|
|
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
|
|
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,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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
9
|
-
const
|
|
10
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|