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 CHANGED
@@ -13,7 +13,7 @@
13
13
  <img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
14
14
  </a>
15
15
  <a href="plugin/pyproject.toml">
16
- <img src="https://img.shields.io/badge/version-0.2.35-green.svg" alt="Version">
16
+ <img src="https://img.shields.io/badge/version-0.2.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`).
@@ -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 userId = readReflexioEnvValue(REFLEXIO_USER_ID_ENV) || crypto.randomUUID();
292
- const added = setReflexioEnvVars({
273
+ const values = {
293
274
  REFLEXIO_URL: reflexioUrl,
294
275
  REFLEXIO_API_KEY: apiKey,
295
- [REFLEXIO_USER_ID_ENV]: userId,
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, REFLEXIO_API_KEY, and REFLEXIO_USER_ID instead.",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.35",
3
+ "version": "0.2.36",
4
4
  "description": "Self-improving Claude Code plugin — learns from corrections via reflexio",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.35",
3
+ "version": "0.2.36",
4
4
  "description": "Self-improving Claude Code plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.35",
3
+ "version": "0.2.36",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
package/plugin/README.md CHANGED
@@ -16,6 +16,9 @@ under `~/.claude-smart/` when they are missing. If Node.js is already installed,
16
16
  `npx claude-smart install` is equivalent; if uv is already installed,
17
17
  `uvx claude-smart install` is equivalent.
18
18
 
19
+ Add `--read-only` to either install command to skip hooks that publish
20
+ interactions for learning.
21
+
19
22
  Supported vanilla native targets are Apple Silicon macOS 14+ and Windows x64.
20
23
  Intel Mac, macOS 13 or older, and Windows ARM fail early because the local
21
24
  embedding/ML dependency stack does not provide a complete native wheel set.
@@ -206,6 +206,21 @@ export default function ConfigureEnvPage() {
206
206
  />
207
207
  </div>
208
208
 
209
+ <div className="flex items-start justify-between gap-4">
210
+ <div className="min-w-0">
211
+ <Label htmlFor="read-only-mode">CLAUDE_SMART_READ_ONLY</Label>
212
+ <p className="text-xs text-muted-foreground mt-0.5">
213
+ Keep session context available but skip publishing
214
+ interactions to Reflexio.
215
+ </p>
216
+ </div>
217
+ <Switch
218
+ id="read-only-mode"
219
+ checked={!!config.CLAUDE_SMART_READ_ONLY}
220
+ onCheckedChange={(v) => update("CLAUDE_SMART_READ_ONLY", v)}
221
+ />
222
+ </div>
223
+
209
224
  <div className="space-y-2">
210
225
  <Label>CLAUDE_SMART_CLI_PATH</Label>
211
226
  <Input
@@ -11,9 +11,9 @@ import type { ClaudeSmartConfig } from "./types";
11
11
  const KNOWN_KEYS = [
12
12
  "REFLEXIO_URL",
13
13
  "REFLEXIO_API_KEY",
14
- "REFLEXIO_USER_ID",
15
14
  "CLAUDE_SMART_USE_LOCAL_CLI",
16
15
  "CLAUDE_SMART_USE_LOCAL_EMBEDDING",
16
+ "CLAUDE_SMART_READ_ONLY",
17
17
  "CLAUDE_SMART_CLI_PATH",
18
18
  "CLAUDE_SMART_CLI_TIMEOUT",
19
19
  "CLAUDE_SMART_STATE_DIR",
@@ -24,6 +24,7 @@ const KNOWN = new Set<string>(KNOWN_KEYS);
24
24
  const BOOL_KEYS = new Set([
25
25
  "CLAUDE_SMART_USE_LOCAL_CLI",
26
26
  "CLAUDE_SMART_USE_LOCAL_EMBEDDING",
27
+ "CLAUDE_SMART_READ_ONLY",
27
28
  ]);
28
29
 
29
30
  function envPath(): string {
@@ -50,9 +51,9 @@ export async function readConfig(): Promise<ClaudeSmartConfig> {
50
51
  const defaults: ClaudeSmartConfig = {
51
52
  REFLEXIO_URL: "http://localhost:8071/",
52
53
  REFLEXIO_API_KEY: "",
53
- REFLEXIO_USER_ID: "",
54
54
  CLAUDE_SMART_USE_LOCAL_CLI: false,
55
55
  CLAUDE_SMART_USE_LOCAL_EMBEDDING: false,
56
+ CLAUDE_SMART_READ_ONLY: false,
56
57
  CLAUDE_SMART_CLI_PATH: "",
57
58
  CLAUDE_SMART_CLI_TIMEOUT: "120",
58
59
  CLAUDE_SMART_STATE_DIR: "",
@@ -114,10 +114,10 @@ export interface SessionDetail {
114
114
  export interface ClaudeSmartConfig {
115
115
  REFLEXIO_URL: string;
116
116
  REFLEXIO_API_KEY: string;
117
- REFLEXIO_USER_ID: string;
118
117
  REFLEXIO_API_KEY_SET?: boolean;
119
118
  CLAUDE_SMART_USE_LOCAL_CLI: boolean;
120
119
  CLAUDE_SMART_USE_LOCAL_EMBEDDING: boolean;
120
+ CLAUDE_SMART_READ_ONLY: boolean;
121
121
  CLAUDE_SMART_CLI_PATH: string;
122
122
  CLAUDE_SMART_CLI_TIMEOUT: string;
123
123
  CLAUDE_SMART_STATE_DIR: string;
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claude-smart"
3
- version = "0.2.35"
3
+ version = "0.2.36"
4
4
  description = "Self-improving Claude Code plugin — learns from corrections via reflexio"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -63,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 identity: file always wins so a rotated key/url overrides
66
+ # Managed Reflexio config: file always wins so a rotated key/url overrides
67
67
  # whatever a long-lived managed backend or dashboard process inherited.
68
- REFLEXIO_URL|REFLEXIO_API_KEY|REFLEXIO_USER_ID)
68
+ REFLEXIO_URL|REFLEXIO_API_KEY)
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
- REFLEXIO_USER_ID_ENV = "REFLEXIO_USER_ID"
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
- Managed Reflexio installs are user-scoped instead of project-scoped:
34
- when ``REFLEXIO_API_KEY`` is configured, the persisted
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``. Local mode
33
- uses the project name; managed/API-key mode uses the persisted
34
- ``REFLEXIO_USER_ID`` UUID. ``agent_version`` is hardcoded to
35
- ``"claude-code"`` in the adapter so skills roll up
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 user id: local installs use
254
- the project name, while managed installs use ``REFLEXIO_USER_ID``.
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.35"
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.5"
1182
+ version = "0.59.10"
1183
1183
  source = { registry = "https://pypi.org/simple" }
1184
- sdist = { url = "https://files.pythonhosted.org/packages/b7/67/eba7fad54ff6f5cce6db4e01f596fc68156b5c7e864af0aa07ad48e880a1/json_repair-0.59.5.tar.gz", hash = "sha256:bb886ee054e99066be8a337b67a986b6a50d79be9a5ad37ae81966e698990784", size = 48632, upload-time = "2026-04-24T11:41:38.133Z" }
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/3e/aa/0529dee460b745b93f6abc97b56b7527314c5167ba29ab7a5bd5c08de01f/json_repair-0.59.5-py3-none-any.whl", hash = "sha256:6869965bd1cc1aaaa04dc85865c26fbb76d9a2d83a20010f5eae2563b1567827", size = 47282, upload-time = "2026-04-24T11:41:36.653Z" },
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.0.3"
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/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" },
1718
- { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" },
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.4.1"
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/4d/68/7f98c221940ce783b492ad6140384daf2e2918cd7175009d6a362c22b9ee/sentence_transformers-5.4.1.tar.gz", hash = "sha256:436bcb1182a0ff42a8fb2b1c43498a70d0a75b688d182f2cd0d1dd115af61ddc", size = 428910, upload-time = "2026-04-14T13:34:59.006Z" }
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/c5/d9/3a9b6f2ccdedc9dc00fe37b2fc58f58f8efbff44565cf4bf39d8568bb13a/sentence_transformers-5.4.1-py3-none-any.whl", hash = "sha256:a6d640fc363849b63affb8e140e9d328feabab86f83d58ac3e16b1c28140b790", size = 571311, upload-time = "2026-04-14T13:34:57.731Z" },
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.7.0"
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/4d/fe/7e84d20ac7d4d5d14bac2eab5976088d86342959fc2c0da54b4c2fc99856/transformers-5.7.0.tar.gz", hash = "sha256:a9d35cf39804e3456c1f9bc1a79ad5ffa878640a61f51f66f71c97f4b4e2ce10", size = 8401287, upload-time = "2026-04-28T18:30:09.75Z" }
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/60/60/86a9fe3037bec221094e2acb680219ad88b77006edba42fc0407a577ca93/transformers-5.7.0-py3-none-any.whl", hash = "sha256:869660cd8fc92badc041f5551bf755a42f4b9558c93341bf3fa3eeed7065079c", size = 10474236, upload-time = "2026-04-28T18:30:05.655Z" },
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]]