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 +1 -1
- package/bin/claude-smart.js +145 -115
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +3 -0
- package/plugin/dashboard/app/configure/env/page.tsx +15 -0
- package/plugin/dashboard/lib/config-file.ts +3 -2
- package/plugin/dashboard/lib/types.ts +1 -1
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +6 -6
- package/plugin/scripts/codex-hook.js +1 -0
- package/plugin/scripts/smart-install.sh +1 -39
- package/plugin/src/claude_smart/cli.py +137 -92
- package/plugin/src/claude_smart/env_config.py +6 -1
- package/plugin/src/claude_smart/events/pre_tool.py +1 -1
- package/plugin/src/claude_smart/events/session_end.py +5 -2
- package/plugin/src/claude_smart/events/stop.py +27 -7
- package/plugin/src/claude_smart/events/user_prompt.py +1 -1
- package/plugin/src/claude_smart/hook.py +22 -2
- package/plugin/src/claude_smart/hook_log.py +7 -2
- package/plugin/src/claude_smart/ids.py +23 -26
- package/plugin/src/claude_smart/internal_call.py +6 -6
- package/plugin/src/claude_smart/publish.py +4 -5
- package/plugin/src/claude_smart/reflexio_adapter.py +2 -3
- package/plugin/src/claude_smart/state.py +9 -0
- package/plugin/uv.lock +16 -13
- package/scripts/setup-claude-smart.sh +329 -0
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.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">
|
package/bin/claude-smart.js
CHANGED
|
@@ -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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
288
|
-
|
|
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
|
|
249
|
+
process.env.REFLEXIO_URL = (fileUrl || process.env.REFLEXIO_URL || MANAGED_REFLEXIO_URL).trim();
|
|
304
250
|
process.env[MANAGED_SETUP_ENV] = "1";
|
|
305
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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__",
|
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;
|
package/plugin/pyproject.toml
CHANGED
package/plugin/scripts/_lib.sh
CHANGED
|
@@ -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
|
|
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
|
-
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
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() {
|
|
@@ -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|' \
|