claude-smart 0.2.35 → 0.2.36
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 +6 -1
- package/bin/claude-smart.js +67 -28
- package/package.json +1 -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 +3 -3
- package/plugin/scripts/codex-hook.js +1 -1
- package/plugin/scripts/smart-install.sh +0 -15
- package/plugin/src/claude_smart/cli.py +90 -20
- package/plugin/src/claude_smart/env_config.py +6 -1
- package/plugin/src/claude_smart/events/session_end.py +4 -1
- package/plugin/src/claude_smart/events/stop.py +4 -1
- package/plugin/src/claude_smart/hook.py +2 -1
- package/plugin/src/claude_smart/ids.py +2 -31
- 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/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.36-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">
|
|
@@ -81,6 +81,9 @@ Four things this changes:
|
|
|
81
81
|
npx claude-smart install # or: uvx claude-smart install
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
Use `--read-only` to install context-loading hooks without the hooks that
|
|
85
|
+
publish interactions for learning.
|
|
86
|
+
|
|
84
87
|
Then restart Claude Code.
|
|
85
88
|
|
|
86
89
|
Requires Node.js (for `npx`) or uv (for `uvx`) to already exist.
|
|
@@ -110,6 +113,8 @@ claude plugin uninstall claude-smart@reflexioai
|
|
|
110
113
|
npx claude-smart install --host codex
|
|
111
114
|
```
|
|
112
115
|
|
|
116
|
+
Add `--read-only` to skip the publish interactions hook.
|
|
117
|
+
|
|
113
118
|
Then fully quit and reopen Codex so hooks reload.
|
|
114
119
|
|
|
115
120
|
Requires the `codex` CLI on `PATH` and Node.js (for `npx`).
|
package/bin/claude-smart.js
CHANGED
|
@@ -39,8 +39,8 @@ 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
44
|
const REFLEXIO_DIR = join(homedir(), ".reflexio");
|
|
45
45
|
const CLAUDE_SMART_STATE_DIR = join(homedir(), ".claude-smart");
|
|
46
46
|
const CODEX_CONFIG_PATH = join(homedir(), ".codex", "config.toml");
|
|
@@ -258,25 +258,6 @@ function setReflexioEnvVars(values) {
|
|
|
258
258
|
return added;
|
|
259
259
|
}
|
|
260
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;
|
|
276
|
-
}
|
|
277
|
-
return "";
|
|
278
|
-
}
|
|
279
|
-
|
|
280
261
|
function maskSecret(value) {
|
|
281
262
|
if (!value) return "";
|
|
282
263
|
if (value.length <= 8) return "*".repeat(value.length);
|
|
@@ -286,26 +267,33 @@ function maskSecret(value) {
|
|
|
286
267
|
|
|
287
268
|
function configureReflexioSetup(args) {
|
|
288
269
|
const apiKey = parseOptionalArg(args, "--api-key").trim();
|
|
270
|
+
const readOnly = parseReadOnly(args);
|
|
289
271
|
if (apiKey) {
|
|
290
272
|
const reflexioUrl = parseOptionalArg(args, "--reflexio-url") || MANAGED_REFLEXIO_URL;
|
|
291
|
-
const
|
|
292
|
-
const added = setReflexioEnvVars({
|
|
273
|
+
const values = {
|
|
293
274
|
REFLEXIO_URL: reflexioUrl,
|
|
294
275
|
REFLEXIO_API_KEY: apiKey,
|
|
295
|
-
|
|
296
|
-
|
|
276
|
+
};
|
|
277
|
+
if (readOnly) values[CLAUDE_SMART_READ_ONLY_ENV] = "1";
|
|
278
|
+
const added = setReflexioEnvVars(values);
|
|
297
279
|
const changed = added.length > 0 ? added.join(", ") : "managed Reflexio settings";
|
|
298
280
|
process.stdout.write(
|
|
299
281
|
`Configured ${REFLEXIO_ENV_PATH} for managed Reflexio (${changed}; API key ${maskSecret(apiKey)}).\n`,
|
|
300
282
|
);
|
|
301
283
|
process.env.REFLEXIO_URL = reflexioUrl;
|
|
302
284
|
process.env.REFLEXIO_API_KEY = apiKey;
|
|
303
|
-
process.env[REFLEXIO_USER_ID_ENV] = userId;
|
|
304
285
|
process.env[MANAGED_SETUP_ENV] = "1";
|
|
305
286
|
return;
|
|
306
287
|
}
|
|
307
288
|
|
|
308
289
|
const added = seedReflexioEnv();
|
|
290
|
+
if (readOnly) {
|
|
291
|
+
added.push(
|
|
292
|
+
...setReflexioEnvVars({
|
|
293
|
+
[CLAUDE_SMART_READ_ONLY_ENV]: "1",
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
296
|
+
}
|
|
309
297
|
if (added.length > 0) {
|
|
310
298
|
process.stdout.write(`Seeded ${REFLEXIO_ENV_PATH} with ${added.join(", ")}.\n`);
|
|
311
299
|
}
|
|
@@ -752,6 +740,38 @@ function quoteCommandPart(part) {
|
|
|
752
740
|
return `"${String(part).replace(/"/g, '\\"')}"`;
|
|
753
741
|
}
|
|
754
742
|
|
|
743
|
+
function commandIsPublishHook(command) {
|
|
744
|
+
if (typeof command !== "string") return false;
|
|
745
|
+
return (
|
|
746
|
+
/hook_entry\.sh\b[\s"']+(?:codex|claude-code)[\s"']+(?:stop|session-end)\b/.test(command) ||
|
|
747
|
+
/codex-hook\.js"?(?:\s+"?hook"?){1}\s+"?(?:stop|session-end)"?/.test(command)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function prunePublishHooksForReadOnly(pluginRoot) {
|
|
752
|
+
for (const hookFile of ["hooks.json", "codex-hooks.json"]) {
|
|
753
|
+
const hookPath = join(pluginRoot, "hooks", hookFile);
|
|
754
|
+
if (!existsSync(hookPath)) continue;
|
|
755
|
+
const parsed = JSON.parse(readFileSync(hookPath, "utf8"));
|
|
756
|
+
const hooksByEvent = parsed.hooks || {};
|
|
757
|
+
for (const event of Object.keys(hooksByEvent)) {
|
|
758
|
+
const blocks = [];
|
|
759
|
+
for (const block of hooksByEvent[event] || []) {
|
|
760
|
+
const keptHooks = (block.hooks || []).filter(
|
|
761
|
+
(hook) => !commandIsPublishHook(hook && hook.command),
|
|
762
|
+
);
|
|
763
|
+
if (keptHooks.length > 0) blocks.push({ ...block, hooks: keptHooks });
|
|
764
|
+
}
|
|
765
|
+
if (blocks.length > 0) {
|
|
766
|
+
hooksByEvent[event] = blocks;
|
|
767
|
+
} else {
|
|
768
|
+
delete hooksByEvent[event];
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
writeFileSync(hookPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
755
775
|
function patchCodexHooksForNode(pluginRoot, nodePath) {
|
|
756
776
|
const hookPath = join(pluginRoot, "hooks", "codex-hooks.json");
|
|
757
777
|
const parsed = JSON.parse(readFileSync(hookPath, "utf8"));
|
|
@@ -796,11 +816,12 @@ function ensurePluginRoot(pluginRoot) {
|
|
|
796
816
|
}
|
|
797
817
|
}
|
|
798
818
|
|
|
799
|
-
async function bootstrapPluginRuntime(pluginRoot) {
|
|
819
|
+
async function bootstrapPluginRuntime(pluginRoot, options = {}) {
|
|
800
820
|
assertSupportedRuntimePlatform();
|
|
801
821
|
process.stdout.write("Preparing claude-smart runtime for hooks...\n");
|
|
802
822
|
const nodeRuntime = await ensurePrivateNode();
|
|
803
823
|
patchCodexHooksForNode(pluginRoot, nodeRuntime.node);
|
|
824
|
+
if (options.readOnly) prunePublishHooksForReadOnly(pluginRoot);
|
|
804
825
|
ensurePluginRoot(pluginRoot);
|
|
805
826
|
const uv = await ensureUv();
|
|
806
827
|
const env = runtimeEnv([dirname(uv), ...privateNodeBinDirs()]);
|
|
@@ -850,6 +871,7 @@ function printHelp() {
|
|
|
850
871
|
" npx claude-smart install --host codex Register the plugin marketplace for Codex",
|
|
851
872
|
" npx claude-smart install --source <owner/repo> Override the marketplace source",
|
|
852
873
|
" npx claude-smart install --api-key <key> Use managed Reflexio service",
|
|
874
|
+
" npx claude-smart install --read-only Install without publish interactions hooks",
|
|
853
875
|
" npx claude-smart uninstall --host codex Remove the Codex marketplace registration",
|
|
854
876
|
" npx claude-smart --help Show this help",
|
|
855
877
|
"",
|
|
@@ -858,7 +880,7 @@ function printHelp() {
|
|
|
858
880
|
` 2. claude plugin install ${PLUGIN_SPEC}`,
|
|
859
881
|
" 3. Appends CLAUDE_SMART_USE_LOCAL_CLI=1 and CLAUDE_SMART_USE_LOCAL_EMBEDDING=1",
|
|
860
882
|
" to ~/.reflexio/.env (idempotent).",
|
|
861
|
-
" Passing --api-key writes REFLEXIO_URL
|
|
883
|
+
" Passing --api-key writes REFLEXIO_URL and REFLEXIO_API_KEY instead.",
|
|
862
884
|
"",
|
|
863
885
|
"Codex install:",
|
|
864
886
|
` 1. Copies the bundled marketplace to ${CODEX_MARKETPLACE_DIR}`,
|
|
@@ -917,6 +939,10 @@ function parseHost(args) {
|
|
|
917
939
|
return value;
|
|
918
940
|
}
|
|
919
941
|
|
|
942
|
+
function parseReadOnly(args) {
|
|
943
|
+
return args.includes("--read-only");
|
|
944
|
+
}
|
|
945
|
+
|
|
920
946
|
function copyCodexMarketplace() {
|
|
921
947
|
for (const rel of CODEX_REQUIRED_FILES) {
|
|
922
948
|
const path = join(PACKAGE_ROOT, rel);
|
|
@@ -1368,6 +1394,7 @@ async function runInstall(args) {
|
|
|
1368
1394
|
}
|
|
1369
1395
|
|
|
1370
1396
|
const source = parseSource(args);
|
|
1397
|
+
const readOnly = parseReadOnly(args);
|
|
1371
1398
|
configureReflexioSetup(args);
|
|
1372
1399
|
|
|
1373
1400
|
const steps = [
|
|
@@ -1387,6 +1414,10 @@ async function runInstall(args) {
|
|
|
1387
1414
|
|
|
1388
1415
|
try {
|
|
1389
1416
|
const pluginRoot = await bootstrapClaudeCodeInstall();
|
|
1417
|
+
if (readOnly) {
|
|
1418
|
+
prunePublishHooksForReadOnly(pluginRoot);
|
|
1419
|
+
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
1420
|
+
}
|
|
1390
1421
|
process.stdout.write(`Prepared claude-smart runtime at ${pluginRoot}.\n`);
|
|
1391
1422
|
if (refreshDashboardService(pluginRoot)) {
|
|
1392
1423
|
process.stdout.write("Refreshed claude-smart dashboard service.\n");
|
|
@@ -1417,9 +1448,13 @@ async function runInstallCodex(args) {
|
|
|
1417
1448
|
process.stderr.write("error: 'codex' CLI not found on PATH. Install Codex first.\n");
|
|
1418
1449
|
process.exit(1);
|
|
1419
1450
|
}
|
|
1451
|
+
const readOnly = parseReadOnly(args);
|
|
1420
1452
|
configureReflexioSetup(args);
|
|
1421
1453
|
|
|
1422
1454
|
const marketplaceRoot = copyCodexMarketplace();
|
|
1455
|
+
if (readOnly) {
|
|
1456
|
+
prunePublishHooksForReadOnly(codexMarketplacePluginRoot(marketplaceRoot));
|
|
1457
|
+
}
|
|
1423
1458
|
process.stdout.write(`Prepared Codex marketplace at ${marketplaceRoot}.\n`);
|
|
1424
1459
|
|
|
1425
1460
|
let code = await runCodex(["plugin", "marketplace", "add", marketplaceRoot]);
|
|
@@ -1460,7 +1495,10 @@ async function runInstallCodex(args) {
|
|
|
1460
1495
|
try {
|
|
1461
1496
|
cacheDir = installCodexPluginCache(codexMarketplacePluginRoot(marketplaceRoot));
|
|
1462
1497
|
process.stdout.write(`Installed Codex plugin cache at ${cacheDir}.\n`);
|
|
1463
|
-
await bootstrapPluginRuntime(cacheDir);
|
|
1498
|
+
await bootstrapPluginRuntime(cacheDir, { readOnly });
|
|
1499
|
+
if (readOnly) {
|
|
1500
|
+
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
1501
|
+
}
|
|
1464
1502
|
if (refreshDashboardService(cacheDir)) {
|
|
1465
1503
|
process.stdout.write("Refreshed claude-smart dashboard service.\n");
|
|
1466
1504
|
}
|
|
@@ -1581,4 +1619,5 @@ module.exports = {
|
|
|
1581
1619
|
configureReflexioSetup,
|
|
1582
1620
|
patchCodexHooksForNode,
|
|
1583
1621
|
platformSupportError,
|
|
1622
|
+
prunePublishHooksForReadOnly,
|
|
1584
1623
|
};
|
package/package.json
CHANGED
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,15 +63,15 @@ 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
|
|
68
|
+
REFLEXIO_URL|REFLEXIO_API_KEY)
|
|
69
69
|
value="$(claude_smart_env_unquote "$value")"
|
|
70
70
|
export "$key=$value"
|
|
71
71
|
;;
|
|
72
72
|
# CLAUDE_SMART_* flags: respect anything the caller already exported so
|
|
73
73
|
# per-session overrides (e.g., a manual test) take precedence over the file.
|
|
74
|
-
CLAUDE_SMART_USE_LOCAL_CLI|CLAUDE_SMART_USE_LOCAL_EMBEDDING|CLAUDE_SMART_BACKEND_AUTOSTART|CLAUDE_SMART_DASHBOARD_AUTOSTART|CLAUDE_SMART_CLI_PATH|CLAUDE_SMART_CLI_TIMEOUT|CLAUDE_SMART_STATE_DIR|CLAUDE_SMART_ENABLE_OPTIMIZER)
|
|
74
|
+
CLAUDE_SMART_USE_LOCAL_CLI|CLAUDE_SMART_USE_LOCAL_EMBEDDING|CLAUDE_SMART_READ_ONLY|CLAUDE_SMART_BACKEND_AUTOSTART|CLAUDE_SMART_DASHBOARD_AUTOSTART|CLAUDE_SMART_CLI_PATH|CLAUDE_SMART_CLI_TIMEOUT|CLAUDE_SMART_STATE_DIR|CLAUDE_SMART_ENABLE_OPTIMIZER)
|
|
75
75
|
if [ -z "$(eval "printf '%s' \"\${$key:-}\"")" ]; then
|
|
76
76
|
value="$(claude_smart_env_unquote "$value")"
|
|
77
77
|
export "$key=$value"
|
|
@@ -183,11 +183,11 @@ function loadReflexioEnv() {
|
|
|
183
183
|
const managedReflexioKeys = new Set([
|
|
184
184
|
"REFLEXIO_URL",
|
|
185
185
|
"REFLEXIO_API_KEY",
|
|
186
|
-
"REFLEXIO_USER_ID",
|
|
187
186
|
]);
|
|
188
187
|
const localConfigKeys = new Set([
|
|
189
188
|
"CLAUDE_SMART_USE_LOCAL_CLI",
|
|
190
189
|
"CLAUDE_SMART_USE_LOCAL_EMBEDDING",
|
|
190
|
+
"CLAUDE_SMART_READ_ONLY",
|
|
191
191
|
"CLAUDE_SMART_BACKEND_AUTOSTART",
|
|
192
192
|
"CLAUDE_SMART_DASHBOARD_AUTOSTART",
|
|
193
193
|
"CLAUDE_SMART_CLI_PATH",
|
|
@@ -472,25 +472,10 @@ claude_smart_env_upsert() {
|
|
|
472
472
|
fi
|
|
473
473
|
}
|
|
474
474
|
|
|
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
475
|
if [ "${CLAUDE_SMART_MANAGED_SETUP:-}" = "1" ] && [ -n "${REFLEXIO_API_KEY:-}" ]; then
|
|
488
476
|
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
477
|
claude_smart_env_upsert REFLEXIO_URL "$REFLEXIO_URL"
|
|
492
478
|
claude_smart_env_upsert REFLEXIO_API_KEY "$REFLEXIO_API_KEY"
|
|
493
|
-
claude_smart_env_upsert REFLEXIO_USER_ID "$REFLEXIO_USER_ID"
|
|
494
479
|
chmod 600 "$REFLEXIO_ENV"
|
|
495
480
|
echo "[claude-smart] configured managed Reflexio in $REFLEXIO_ENV" >&2
|
|
496
481
|
elif [ -z "${REFLEXIO_API_KEY:-}" ]; then
|
|
@@ -30,7 +30,6 @@ import shutil
|
|
|
30
30
|
import subprocess
|
|
31
31
|
import sys
|
|
32
32
|
import time
|
|
33
|
-
import uuid
|
|
34
33
|
from dataclasses import dataclass
|
|
35
34
|
from pathlib import Path
|
|
36
35
|
|
|
@@ -145,34 +144,15 @@ def _seed_reflexio_env() -> list[str]:
|
|
|
145
144
|
|
|
146
145
|
def _seed_managed_reflexio_env(*, api_key: str, reflexio_url: str) -> list[str]:
|
|
147
146
|
"""Write managed Reflexio connection settings to ``~/.reflexio/.env``."""
|
|
148
|
-
user_id = _read_reflexio_env_value(env_config.REFLEXIO_USER_ID_ENV) or str(
|
|
149
|
-
uuid.uuid4()
|
|
150
|
-
)
|
|
151
147
|
return env_config.set_env_vars(
|
|
152
148
|
_REFLEXIO_ENV_PATH,
|
|
153
149
|
{
|
|
154
150
|
env_config.REFLEXIO_URL_ENV: reflexio_url,
|
|
155
151
|
env_config.REFLEXIO_API_KEY_ENV: api_key,
|
|
156
|
-
env_config.REFLEXIO_USER_ID_ENV: user_id,
|
|
157
152
|
},
|
|
158
153
|
)
|
|
159
154
|
|
|
160
155
|
|
|
161
|
-
def _read_reflexio_env_value(key: str) -> str:
|
|
162
|
-
try:
|
|
163
|
-
text = _REFLEXIO_ENV_PATH.read_text()
|
|
164
|
-
except OSError:
|
|
165
|
-
return ""
|
|
166
|
-
for line in text.splitlines():
|
|
167
|
-
parsed = env_config.parse_env_line(line)
|
|
168
|
-
if parsed is None:
|
|
169
|
-
continue
|
|
170
|
-
parsed_key, value = parsed
|
|
171
|
-
if parsed_key == key:
|
|
172
|
-
return value
|
|
173
|
-
return ""
|
|
174
|
-
|
|
175
|
-
|
|
176
156
|
def _semver_tuple(path: Path) -> tuple[int, int, int] | None:
|
|
177
157
|
parts = path.name.split(".", 2)
|
|
178
158
|
if len(parts) != 3:
|
|
@@ -328,6 +308,54 @@ def _prepare_codex_local_marketplace() -> Path:
|
|
|
328
308
|
return marketplace_root
|
|
329
309
|
|
|
330
310
|
|
|
311
|
+
def _command_is_publish_hook(command: object) -> bool:
|
|
312
|
+
if not isinstance(command, str):
|
|
313
|
+
return False
|
|
314
|
+
return bool(
|
|
315
|
+
re.search(
|
|
316
|
+
r"hook_entry\.sh\b[\s\"']+(?:codex|claude-code)[\s\"']+(?:stop|session-end)\b",
|
|
317
|
+
command,
|
|
318
|
+
)
|
|
319
|
+
or re.search(
|
|
320
|
+
r"codex-hook\.js\"?(?:\s+\"?hook\"?){1}\s+\"?(?:stop|session-end)\"?",
|
|
321
|
+
command,
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _prune_publish_hooks_for_read_only(plugin_root: Path) -> None:
|
|
327
|
+
"""Remove publish-to-reflexio hook commands from a copied/installed plugin."""
|
|
328
|
+
for hook_file in ("hooks.json", "codex-hooks.json"):
|
|
329
|
+
hook_path = plugin_root / "hooks" / hook_file
|
|
330
|
+
if not hook_path.is_file():
|
|
331
|
+
continue
|
|
332
|
+
parsed = json.loads(hook_path.read_text())
|
|
333
|
+
hooks_by_event = parsed.get("hooks")
|
|
334
|
+
if not isinstance(hooks_by_event, dict):
|
|
335
|
+
continue
|
|
336
|
+
for event in list(hooks_by_event):
|
|
337
|
+
blocks = []
|
|
338
|
+
for block in hooks_by_event.get(event) or []:
|
|
339
|
+
if not isinstance(block, dict):
|
|
340
|
+
continue
|
|
341
|
+
kept_hooks = [
|
|
342
|
+
hook
|
|
343
|
+
for hook in block.get("hooks") or []
|
|
344
|
+
if not _command_is_publish_hook(
|
|
345
|
+
hook.get("command") if isinstance(hook, dict) else None
|
|
346
|
+
)
|
|
347
|
+
]
|
|
348
|
+
if kept_hooks:
|
|
349
|
+
next_block = dict(block)
|
|
350
|
+
next_block["hooks"] = kept_hooks
|
|
351
|
+
blocks.append(next_block)
|
|
352
|
+
if blocks:
|
|
353
|
+
hooks_by_event[event] = blocks
|
|
354
|
+
else:
|
|
355
|
+
del hooks_by_event[event]
|
|
356
|
+
hook_path.write_text(json.dumps(parsed, indent=2) + "\n")
|
|
357
|
+
|
|
358
|
+
|
|
331
359
|
def _run_codex(args: list[str]) -> subprocess.CompletedProcess[str]:
|
|
332
360
|
"""Invoke the ``codex`` CLI with a hard timeout.
|
|
333
361
|
|
|
@@ -797,11 +825,19 @@ def _register_codex_marketplace(root: Path) -> tuple[bool, str]:
|
|
|
797
825
|
|
|
798
826
|
def _configure_reflexio_setup(args: argparse.Namespace) -> None:
|
|
799
827
|
api_key = (getattr(args, "api_key", "") or "").strip()
|
|
828
|
+
read_only = getattr(args, "read_only", None)
|
|
800
829
|
if api_key:
|
|
801
830
|
reflexio_url = (
|
|
802
831
|
getattr(args, "reflexio_url", "") or _MANAGED_REFLEXIO_URL
|
|
803
832
|
).strip()
|
|
804
833
|
added = _seed_managed_reflexio_env(api_key=api_key, reflexio_url=reflexio_url)
|
|
834
|
+
if read_only:
|
|
835
|
+
added.extend(
|
|
836
|
+
env_config.set_env_vars(
|
|
837
|
+
_REFLEXIO_ENV_PATH,
|
|
838
|
+
{env_config.CLAUDE_SMART_READ_ONLY_ENV: "1"},
|
|
839
|
+
)
|
|
840
|
+
)
|
|
805
841
|
changed = ", ".join(added) if added else "managed Reflexio settings"
|
|
806
842
|
sys.stdout.write(
|
|
807
843
|
f"Configured {_REFLEXIO_ENV_PATH} for managed Reflexio "
|
|
@@ -810,6 +846,13 @@ def _configure_reflexio_setup(args: argparse.Namespace) -> None:
|
|
|
810
846
|
return
|
|
811
847
|
|
|
812
848
|
added = _seed_reflexio_env()
|
|
849
|
+
if read_only:
|
|
850
|
+
added.extend(
|
|
851
|
+
env_config.set_env_vars(
|
|
852
|
+
_REFLEXIO_ENV_PATH,
|
|
853
|
+
{env_config.CLAUDE_SMART_READ_ONLY_ENV: "1"},
|
|
854
|
+
)
|
|
855
|
+
)
|
|
813
856
|
if added:
|
|
814
857
|
sys.stdout.write(f"Seeded {_REFLEXIO_ENV_PATH} with {', '.join(added)}.\n")
|
|
815
858
|
|
|
@@ -846,6 +889,11 @@ def cmd_install_codex(args: argparse.Namespace) -> int:
|
|
|
846
889
|
)
|
|
847
890
|
return 1
|
|
848
891
|
marketplace_root = _prepare_codex_local_marketplace()
|
|
892
|
+
read_only = bool(getattr(args, "read_only", False))
|
|
893
|
+
if read_only:
|
|
894
|
+
_prune_publish_hooks_for_read_only(
|
|
895
|
+
marketplace_root / _CODEX_LOCAL_PLUGIN_PATH
|
|
896
|
+
)
|
|
849
897
|
|
|
850
898
|
hooks_ok, hooks_msg = _enable_codex_plugin_hooks()
|
|
851
899
|
if hooks_ok:
|
|
@@ -869,6 +917,10 @@ def cmd_install_codex(args: argparse.Namespace) -> int:
|
|
|
869
917
|
)
|
|
870
918
|
if installed:
|
|
871
919
|
sys.stdout.write(f"{install_msg}.\n")
|
|
920
|
+
if read_only:
|
|
921
|
+
sys.stdout.write(
|
|
922
|
+
"Installed read-only hook manifest; publish interactions hooks are disabled.\n"
|
|
923
|
+
)
|
|
872
924
|
trusted, trust_msg = _trust_codex_plugin_hooks(Path.cwd())
|
|
873
925
|
if not trusted:
|
|
874
926
|
# The app-server can race against the just-installed cache.
|
|
@@ -957,6 +1009,11 @@ def cmd_install(args: argparse.Namespace) -> int:
|
|
|
957
1009
|
"Fix the issue above, then run /claude-smart:restart or restart Claude Code to retry.\n"
|
|
958
1010
|
)
|
|
959
1011
|
return 1
|
|
1012
|
+
if bool(getattr(args, "read_only", False)):
|
|
1013
|
+
_prune_publish_hooks_for_read_only(Path(message))
|
|
1014
|
+
sys.stdout.write(
|
|
1015
|
+
"Installed read-only hook manifest; publish interactions hooks are disabled.\n"
|
|
1016
|
+
)
|
|
960
1017
|
sys.stdout.write(f"Prepared claude-smart runtime at {message}.\n")
|
|
961
1018
|
|
|
962
1019
|
sys.stdout.write(
|
|
@@ -1135,6 +1192,12 @@ def cmd_learn(args: argparse.Namespace) -> int:
|
|
|
1135
1192
|
int: 0 on success or no-op (no active session, or nothing to
|
|
1136
1193
|
publish), 1 if reflexio is unreachable.
|
|
1137
1194
|
"""
|
|
1195
|
+
if env_config.env_truthy(env_config.CLAUDE_SMART_READ_ONLY_ENV):
|
|
1196
|
+
sys.stdout.write(
|
|
1197
|
+
"claude-smart is in read-only mode (CLAUDE_SMART_READ_ONLY=1); "
|
|
1198
|
+
"skipping publish.\n"
|
|
1199
|
+
)
|
|
1200
|
+
return 0
|
|
1138
1201
|
session_id = args.session or _latest_session_id()
|
|
1139
1202
|
if not session_id:
|
|
1140
1203
|
sys.stdout.write("No active claude-smart session buffer found.\n")
|
|
@@ -1779,6 +1842,13 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1779
1842
|
default=_MANAGED_REFLEXIO_URL,
|
|
1780
1843
|
help="Managed Reflexio URL used only when --api-key is provided",
|
|
1781
1844
|
)
|
|
1845
|
+
inst.add_argument(
|
|
1846
|
+
"--read-only",
|
|
1847
|
+
action="store_const",
|
|
1848
|
+
const=True,
|
|
1849
|
+
default=None,
|
|
1850
|
+
help="Install without publish interactions hooks",
|
|
1851
|
+
)
|
|
1782
1852
|
inst.set_defaults(func=cmd_install)
|
|
1783
1853
|
|
|
1784
1854
|
upd = sub.add_parser("update", help="Update claude-smart to the latest version")
|
|
@@ -9,7 +9,7 @@ REFLEXIO_ENV_PATH = Path.home() / ".reflexio" / ".env"
|
|
|
9
9
|
MANAGED_REFLEXIO_URL = "https://www.reflexio.ai/"
|
|
10
10
|
REFLEXIO_URL_ENV = "REFLEXIO_URL"
|
|
11
11
|
REFLEXIO_API_KEY_ENV = "REFLEXIO_API_KEY"
|
|
12
|
-
|
|
12
|
+
CLAUDE_SMART_READ_ONLY_ENV = "CLAUDE_SMART_READ_ONLY"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def parse_env_line(line: str) -> tuple[str, str] | None:
|
|
@@ -89,6 +89,11 @@ def set_env_vars(path: Path, values: dict[str, str]) -> list[str]:
|
|
|
89
89
|
return added
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def env_truthy(name: str) -> bool:
|
|
93
|
+
"""Return True when an environment flag is explicitly enabled."""
|
|
94
|
+
return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
|
|
95
|
+
|
|
96
|
+
|
|
92
97
|
def mask_secret(value: str) -> str:
|
|
93
98
|
"""Return a display-safe API key preview."""
|
|
94
99
|
if not value:
|
|
@@ -21,7 +21,7 @@ import time
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
24
|
-
from claude_smart import ids, publish, state
|
|
24
|
+
from claude_smart import env_config, ids, publish, state
|
|
25
25
|
from claude_smart.events.stop import (
|
|
26
26
|
_read_transcript_entries,
|
|
27
27
|
_scan_transcript_for_assistant_text,
|
|
@@ -58,6 +58,9 @@ def handle(payload: dict[str, Any]) -> tuple[publish.PublishStatus, int] | None:
|
|
|
58
58
|
project_id=project_id,
|
|
59
59
|
transcript_path=payload.get("transcript_path"),
|
|
60
60
|
)
|
|
61
|
+
if env_config.env_truthy(env_config.CLAUDE_SMART_READ_ONLY_ENV):
|
|
62
|
+
state.mark_all_published(session_id)
|
|
63
|
+
return ("nothing", 0)
|
|
61
64
|
|
|
62
65
|
# ``force_extraction=True`` makes the reflexio server run the
|
|
63
66
|
# extractor synchronously for this publish, so by the time the HTTP
|
|
@@ -9,7 +9,7 @@ import time
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from claude_smart import cs_cite, ids, internal_call, publish, runtime, state
|
|
12
|
+
from claude_smart import cs_cite, env_config, ids, internal_call, publish, runtime, state
|
|
13
13
|
|
|
14
14
|
_LOGGER = logging.getLogger(__name__)
|
|
15
15
|
|
|
@@ -413,6 +413,9 @@ def handle(payload: dict[str, Any]) -> tuple[publish.PublishStatus, int] | None:
|
|
|
413
413
|
if cited_items:
|
|
414
414
|
record["cited_items"] = cited_items
|
|
415
415
|
state.append(session_id, record)
|
|
416
|
+
if env_config.env_truthy(env_config.CLAUDE_SMART_READ_ONLY_ENV):
|
|
417
|
+
state.mark_all_published(session_id)
|
|
418
|
+
return ("nothing", 0)
|
|
416
419
|
return publish.publish_unpublished(
|
|
417
420
|
session_id=session_id,
|
|
418
421
|
project_id=project_id,
|
|
@@ -24,7 +24,7 @@ from contextlib import redirect_stdout
|
|
|
24
24
|
from io import StringIO
|
|
25
25
|
from typing import Any
|
|
26
26
|
|
|
27
|
-
from claude_smart import hook_log, ids, runtime
|
|
27
|
+
from claude_smart import env_config, hook_log, ids, runtime
|
|
28
28
|
from claude_smart.internal_call import is_internal_invocation
|
|
29
29
|
|
|
30
30
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -94,6 +94,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
94
94
|
argv = argv if argv is not None else sys.argv[1:]
|
|
95
95
|
host, event = _parse_args(argv)
|
|
96
96
|
runtime.set_host(host)
|
|
97
|
+
env_config.load_reflexio_env()
|
|
97
98
|
if not event:
|
|
98
99
|
_LOGGER.warning("hook dispatcher called with no event name")
|
|
99
100
|
emit_continue()
|
|
@@ -19,21 +19,16 @@ from __future__ import annotations
|
|
|
19
19
|
import logging
|
|
20
20
|
import os
|
|
21
21
|
import subprocess # noqa: S404 — git invocation with a fixed flag set.
|
|
22
|
-
import uuid
|
|
23
22
|
from pathlib import Path
|
|
24
23
|
|
|
25
|
-
from claude_smart import env_config
|
|
26
|
-
|
|
27
24
|
_LOGGER = logging.getLogger(__name__)
|
|
28
25
|
|
|
29
26
|
|
|
30
27
|
def resolve_project_id(cwd: str | os.PathLike[str] | None = None) -> str:
|
|
31
28
|
"""Return a stable project identifier for the given working directory.
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
``REFLEXIO_USER_ID`` UUID is returned. Local installs without an API key
|
|
36
|
-
keep using the git/project name.
|
|
30
|
+
Local and managed/API-key installs both use the git/project name as
|
|
31
|
+
Reflexio's ``user_id``.
|
|
37
32
|
|
|
38
33
|
Prefers the basename of the git toplevel (so worktrees, submodules, and
|
|
39
34
|
`cd src/` all still map to the same project). Falls back to the cwd
|
|
@@ -45,10 +40,6 @@ def resolve_project_id(cwd: str | os.PathLike[str] | None = None) -> str:
|
|
|
45
40
|
Returns:
|
|
46
41
|
str: A non-empty identifier. Never raises.
|
|
47
42
|
"""
|
|
48
|
-
managed_user_id = _managed_user_id()
|
|
49
|
-
if managed_user_id:
|
|
50
|
-
return managed_user_id
|
|
51
|
-
|
|
52
43
|
base = Path(cwd) if cwd is not None else Path.cwd()
|
|
53
44
|
try:
|
|
54
45
|
result = subprocess.run( # noqa: S603, S607 — fixed argv, cwd is a Path.
|
|
@@ -66,23 +57,3 @@ def resolve_project_id(cwd: str | os.PathLike[str] | None = None) -> str:
|
|
|
66
57
|
except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
|
|
67
58
|
_LOGGER.debug("git toplevel resolution failed: %s", exc)
|
|
68
59
|
return base.name or "unknown-project"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def _managed_user_id() -> str:
|
|
72
|
-
env_config.load_reflexio_env()
|
|
73
|
-
if not os.environ.get(env_config.REFLEXIO_API_KEY_ENV):
|
|
74
|
-
return ""
|
|
75
|
-
user_id = os.environ.get(env_config.REFLEXIO_USER_ID_ENV, "").strip()
|
|
76
|
-
if user_id:
|
|
77
|
-
return user_id
|
|
78
|
-
|
|
79
|
-
user_id = str(uuid.uuid4())
|
|
80
|
-
try:
|
|
81
|
-
env_config.set_env_vars(
|
|
82
|
-
env_config.REFLEXIO_ENV_PATH,
|
|
83
|
-
{env_config.REFLEXIO_USER_ID_ENV: user_id},
|
|
84
|
-
)
|
|
85
|
-
except OSError as exc:
|
|
86
|
-
_LOGGER.debug("could not persist managed Reflexio user id: %s", exc)
|
|
87
|
-
os.environ[env_config.REFLEXIO_USER_ID_ENV] = user_id
|
|
88
|
-
return user_id
|
|
@@ -29,11 +29,10 @@ def publish_unpublished(
|
|
|
29
29
|
|
|
30
30
|
Args:
|
|
31
31
|
session_id (str): Claude Code session id, attached to each interaction.
|
|
32
|
-
project_id (str): Stable user-scope id resolved by ``ids
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
globally per agent rather than per project.
|
|
32
|
+
project_id (str): Stable user-scope id resolved by ``ids`` from the
|
|
33
|
+
project name. ``agent_version`` is hardcoded to ``"claude-code"``
|
|
34
|
+
in the adapter so skills roll up globally per agent rather than
|
|
35
|
+
per project.
|
|
37
36
|
force_extraction (bool): Whether to ask reflexio to run extraction
|
|
38
37
|
synchronously instead of queuing for the next sweep.
|
|
39
38
|
override_learning_stall (bool): Whether to bypass a recorded
|
|
@@ -250,9 +250,8 @@ class Adapter:
|
|
|
250
250
|
def fetch_user_playbooks(self, *, project_id: str, top_k: int = 10) -> list[Any]:
|
|
251
251
|
"""Fetch CURRENT user playbooks for ``project_id``.
|
|
252
252
|
|
|
253
|
-
User playbooks are scoped by the resolved
|
|
254
|
-
the
|
|
255
|
-
Filtering mirrors the publish path
|
|
253
|
+
User playbooks are scoped by the resolved project id. Filtering mirrors
|
|
254
|
+
the publish path
|
|
256
255
|
(``publish_interaction(user_id=project_id, …)``).
|
|
257
256
|
|
|
258
257
|
Args:
|
|
@@ -150,6 +150,15 @@ def append(session_id: str, record: dict[str, Any]) -> None:
|
|
|
150
150
|
fh.write(line)
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
def mark_all_published(session_id: str) -> None:
|
|
154
|
+
"""Advance the watermark over every currently buffered record.
|
|
155
|
+
|
|
156
|
+
Read-only mode still needs to retire any already-buffered interactions so
|
|
157
|
+
they cannot publish later if learning is re-enabled.
|
|
158
|
+
"""
|
|
159
|
+
append(session_id, {"published_up_to": len(read_all(session_id))})
|
|
160
|
+
|
|
161
|
+
|
|
153
162
|
def read_all(session_id: str) -> list[dict[str, Any]]:
|
|
154
163
|
"""Return every record in the buffer as a list of dicts. Missing file → []."""
|
|
155
164
|
path = session_path(session_id)
|
package/plugin/uv.lock
CHANGED
|
@@ -419,7 +419,7 @@ wheels = [
|
|
|
419
419
|
|
|
420
420
|
[[package]]
|
|
421
421
|
name = "claude-smart"
|
|
422
|
-
version = "0.2.
|
|
422
|
+
version = "0.2.36"
|
|
423
423
|
source = { editable = "." }
|
|
424
424
|
dependencies = [
|
|
425
425
|
{ name = "chromadb" },
|
|
@@ -1179,11 +1179,11 @@ wheels = [
|
|
|
1179
1179
|
|
|
1180
1180
|
[[package]]
|
|
1181
1181
|
name = "json-repair"
|
|
1182
|
-
version = "0.59.
|
|
1182
|
+
version = "0.59.10"
|
|
1183
1183
|
source = { registry = "https://pypi.org/simple" }
|
|
1184
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1184
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d3/7c/e95bb03068572146eba37e8175c760f470ea0a6097310e16bbf2bc6e6457/json_repair-0.59.10.tar.gz", hash = "sha256:2e4b85537c752d8a513ea28fdad891e5ede32c83de745366b97f648b8c34ede7", size = 49133, upload-time = "2026-05-14T06:41:51.222Z" }
|
|
1185
1185
|
wheels = [
|
|
1186
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1186
|
+
{ url = "https://files.pythonhosted.org/packages/ee/87/49b20c6b81493d55c311f711ed87319d0fbad8bd0bbfbe36e52103af36bd/json_repair-0.59.10-py3-none-any.whl", hash = "sha256:5468fa3eaadcc9b4a5646776bc4176e2fe5f374b5848a15f468cce3b60e3db0e", size = 47742, upload-time = "2026-05-14T06:41:49.812Z" },
|
|
1187
1187
|
]
|
|
1188
1188
|
|
|
1189
1189
|
[[package]]
|
|
@@ -1711,11 +1711,14 @@ wheels = [
|
|
|
1711
1711
|
|
|
1712
1712
|
[[package]]
|
|
1713
1713
|
name = "nvidia-cublas"
|
|
1714
|
-
version = "13.1.
|
|
1714
|
+
version = "13.1.1.3"
|
|
1715
1715
|
source = { registry = "https://pypi.org/simple" }
|
|
1716
|
+
dependencies = [
|
|
1717
|
+
{ name = "nvidia-cuda-nvrtc" },
|
|
1718
|
+
]
|
|
1716
1719
|
wheels = [
|
|
1717
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1718
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1720
|
+
{ url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
|
|
1721
|
+
{ url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" },
|
|
1719
1722
|
]
|
|
1720
1723
|
|
|
1721
1724
|
[[package]]
|
|
@@ -3016,7 +3019,7 @@ wheels = [
|
|
|
3016
3019
|
|
|
3017
3020
|
[[package]]
|
|
3018
3021
|
name = "sentence-transformers"
|
|
3019
|
-
version = "5.
|
|
3022
|
+
version = "5.5.1"
|
|
3020
3023
|
source = { registry = "https://pypi.org/simple" }
|
|
3021
3024
|
dependencies = [
|
|
3022
3025
|
{ name = "huggingface-hub" },
|
|
@@ -3028,9 +3031,9 @@ dependencies = [
|
|
|
3028
3031
|
{ name = "transformers" },
|
|
3029
3032
|
{ name = "typing-extensions" },
|
|
3030
3033
|
]
|
|
3031
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
3034
|
+
sdist = { url = "https://files.pythonhosted.org/packages/cf/d4/7ef93157485e978c016f49da05363c1e4e7237beb5343b64b5631101f0f1/sentence_transformers-5.5.1.tar.gz", hash = "sha256:02b7740dfc60bdbbcb6061625f5d97a5c1a4e2d3baac5f9391b912bb5eae2290", size = 445161, upload-time = "2026-05-20T07:37:44.465Z" }
|
|
3032
3035
|
wheels = [
|
|
3033
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
3036
|
+
{ url = "https://files.pythonhosted.org/packages/bf/03/ee99a6b030e7a2e056547729f8a4709dd93e13d9c6f07590f74c395c4017/sentence_transformers-5.5.1-py3-none-any.whl", hash = "sha256:4fe11d433badc5282d32f7fc08bc714216b7a5aca426f9df77a45a554756deb7", size = 588887, upload-time = "2026-05-20T07:37:43.004Z" },
|
|
3034
3037
|
]
|
|
3035
3038
|
|
|
3036
3039
|
[[package]]
|
|
@@ -3281,7 +3284,7 @@ wheels = [
|
|
|
3281
3284
|
|
|
3282
3285
|
[[package]]
|
|
3283
3286
|
name = "transformers"
|
|
3284
|
-
version = "5.
|
|
3287
|
+
version = "5.9.0"
|
|
3285
3288
|
source = { registry = "https://pypi.org/simple" }
|
|
3286
3289
|
dependencies = [
|
|
3287
3290
|
{ name = "huggingface-hub" },
|
|
@@ -3294,9 +3297,9 @@ dependencies = [
|
|
|
3294
3297
|
{ name = "tqdm" },
|
|
3295
3298
|
{ name = "typer" },
|
|
3296
3299
|
]
|
|
3297
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
3300
|
+
sdist = { url = "https://files.pythonhosted.org/packages/51/58/7f843608f2e8421f86bb97060b54649be6239ec612b82bf9d41e65c26c00/transformers-5.9.0.tar.gz", hash = "sha256:25997cb8fa6053533171634b6162d7df54346530ec2aa9b42bb834e63668c842", size = 8642240, upload-time = "2026-05-20T14:50:49.278Z" }
|
|
3298
3301
|
wheels = [
|
|
3299
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
3302
|
+
{ url = "https://files.pythonhosted.org/packages/02/ca/2eaa5359f2ccb8c2e1656bc26305ad0cf438aa392ce4b29ae67a315c186e/transformers-5.9.0-py3-none-any.whl", hash = "sha256:1d19509bcff7028ebc6b277d71caa712e8353778463d38764237d14b42b52788", size = 10787648, upload-time = "2026-05-20T14:50:45.337Z" },
|
|
3300
3303
|
]
|
|
3301
3304
|
|
|
3302
3305
|
[[package]]
|