claude-smart 0.2.48 → 0.2.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +11 -40
  3. package/bin/claude-smart.js +393 -55
  4. package/package.json +3 -2
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/.coverage +0 -0
  8. package/plugin/README.md +4 -3
  9. package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
  10. package/plugin/dashboard/app/dashboard/page.tsx +6 -1
  11. package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
  12. package/plugin/dashboard/app/preferences/page.tsx +32 -35
  13. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
  14. package/plugin/dashboard/app/sessions/page.tsx +2 -0
  15. package/plugin/dashboard/app/skills/page.tsx +65 -50
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
  18. package/plugin/dashboard/components/common/host-badge.tsx +118 -0
  19. package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
  20. package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
  21. package/plugin/dashboard/components/common/page-header.tsx +3 -3
  22. package/plugin/dashboard/lib/config-file.ts +5 -1
  23. package/plugin/dashboard/lib/host-attribution.ts +62 -0
  24. package/plugin/dashboard/lib/session-reader.ts +40 -2
  25. package/plugin/dashboard/lib/types.ts +7 -1
  26. package/plugin/opencode/dist/server.mjs +60 -2
  27. package/plugin/opencode/server.mts +64 -2
  28. package/plugin/pyproject.toml +2 -2
  29. package/plugin/scripts/_lib.sh +255 -7
  30. package/plugin/scripts/backend-python-runner.py +46 -0
  31. package/plugin/scripts/backend-service.sh +766 -123
  32. package/plugin/scripts/codex-hook.js +79 -229
  33. package/plugin/scripts/dashboard-open.sh +6 -4
  34. package/plugin/scripts/dashboard-service.sh +118 -137
  35. package/plugin/scripts/ensure-plugin-root.sh +106 -8
  36. package/plugin/scripts/hook_entry.sh +3 -0
  37. package/plugin/scripts/opencode-claude-compat.js +8 -1
  38. package/plugin/scripts/smart-install.sh +116 -21
  39. package/plugin/src/README.md +1 -1
  40. package/plugin/src/claude_smart/cli.py +93 -29
  41. package/plugin/src/claude_smart/context_inject.py +3 -0
  42. package/plugin/src/claude_smart/env_config.py +56 -11
  43. package/plugin/src/claude_smart/events/post_tool.py +2 -1
  44. package/plugin/src/claude_smart/events/session_end.py +2 -1
  45. package/plugin/src/claude_smart/events/stop.py +3 -0
  46. package/plugin/src/claude_smart/events/user_prompt.py +2 -1
  47. package/plugin/src/claude_smart/internal_call.py +5 -2
  48. package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
  49. package/plugin/src/claude_smart/publish.py +59 -7
  50. package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
  51. package/plugin/src/claude_smart/runtime.py +15 -6
  52. package/plugin/src/claude_smart/state.py +211 -52
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +13 -0
  55. package/plugin/vendor/reflexio/reflexio/README.md +7 -3
  56. package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
  57. package/plugin/vendor/reflexio/reflexio/cli/README.md +3 -0
  58. package/plugin/vendor/reflexio/reflexio/client/client.py +166 -9
  59. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
  60. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
  61. package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
  62. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +195 -25
  63. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
  64. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
  65. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
  66. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
  67. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
  68. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
  69. package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
  70. package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
  71. package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
  72. package/plugin/vendor/reflexio/reflexio/server/api.py +79 -5
  73. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
  74. package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
  75. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
  76. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
  77. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +63 -6
  78. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
  79. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1 -0
  80. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
  81. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
  82. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
  83. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
  84. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +199 -2
  85. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
  86. package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
  87. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
  88. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
  89. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
  90. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
  91. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
  92. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
  93. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
  94. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
  95. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
  96. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
  97. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
  98. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
  99. package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +63 -2
  100. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
  101. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
  102. package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
  103. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
  104. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
  105. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
  106. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
  107. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
  108. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
  109. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
  110. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
  111. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
  112. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
  113. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
  114. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
  115. package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
  116. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/__init__.py +13 -0
  117. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
  118. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +262 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
  120. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
  121. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
  122. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
  123. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
  124. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
  125. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
  126. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +1 -3
  127. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
  128. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +640 -80
  129. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
  130. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
  131. package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
  132. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
  133. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
  134. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
  135. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
  136. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
  137. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
  138. package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
  139. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
  140. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
  141. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
  142. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
  143. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
  144. package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +166 -66
  145. package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
  146. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
  147. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
  148. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
  149. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
  150. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
  151. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
  152. package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
  153. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
  154. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
  155. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  156. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +190 -2
  157. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
  158. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
  159. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +414 -0
  160. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -5
  161. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
  162. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
  163. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
  164. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +86 -2
  165. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
  166. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
  167. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +59 -7
  168. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
  169. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +8 -3
  170. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +57 -12
  171. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +197 -12
  172. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +70 -11
  173. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +17 -0
  174. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_commit_scope.py +9 -0
  175. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
  176. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +269 -0
  177. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
  178. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
  179. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
  180. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
  181. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
  182. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
  183. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
  184. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
  185. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +82 -1
  186. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +74 -2
  187. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
  188. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
  189. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
  190. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
  191. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
  192. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
  193. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
@@ -4,8 +4,9 @@
4
4
  * CLIs. Both Claude Code and Codex install from the bundled marketplace in
5
5
  * this npm package: Claude Code registers the package root as a local
6
6
  * marketplace, and Codex copies the bundled plugin into its own marketplace
7
- * wrapper. Both paths seed ~/.reflexio/.env with the two local-provider flags
8
- * so reflexio can route generation through local tools with no API key.
7
+ * wrapper. The npm path reads setup/bootstrap config from ~/.reflexio/.env and
8
+ * seeds runtime ~/.claude-smart/.env with local-provider defaults so reflexio
9
+ * can route generation through local tools with no API key.
9
10
  * Managed/read-only/global setup is handled by `npx claude-smart setup`,
10
11
  * which writes ~/.reflexio/.env before running this installer.
11
12
  *
@@ -44,15 +45,24 @@ const CODEX_MARKETPLACE_DISPLAY_NAME = "ReflexioAI";
44
45
  const CODEX_PLUGIN_ID = `claude-smart@${CODEX_MARKETPLACE_NAME}`;
45
46
  const OPENCODE_BARE_PLUGIN_SPEC = "claude-smart";
46
47
  const OPENCODE_CONFIG_NAMES = ["opencode.json", "opencode.jsonc"];
47
- const REFLEXIO_ENV_PATH = join(homedir(), ".reflexio", ".env");
48
+ const REFLEXIO_SETUP_ENV_PATH = join(homedir(), ".reflexio", ".env");
49
+ const CLAUDE_SMART_ENV_PATH = join(homedir(), ".claude-smart", ".env");
48
50
  const MANAGED_REFLEXIO_URL = "https://www.reflexio.ai/";
49
51
  const MANAGED_SETUP_ENV = "CLAUDE_SMART_MANAGED_SETUP";
50
52
  const CLAUDE_SMART_READ_ONLY_ENV = "CLAUDE_SMART_READ_ONLY";
51
53
  const CLAUDE_SMART_USE_LOCAL_CLI_ENV = "CLAUDE_SMART_USE_LOCAL_CLI";
52
54
  const CLAUDE_SMART_USE_LOCAL_EMBEDDING_ENV = "CLAUDE_SMART_USE_LOCAL_EMBEDDING";
55
+ const CLAUDE_SMART_HOST_ENV = "CLAUDE_SMART_HOST";
56
+ const CLAUDE_SMART_OPENCODE_PATH_ENV = "CLAUDE_SMART_OPENCODE_PATH";
53
57
  const REFLEXIO_USER_ID_ENV = "REFLEXIO_USER_ID";
58
+ const HOST_CLAUDE_CODE = "claude-code";
59
+ const HOST_CODEX = "codex";
60
+ const HOST_OPENCODE = "opencode";
61
+ const SUPPORTED_HOSTS = [HOST_CLAUDE_CODE, HOST_CODEX, HOST_OPENCODE];
62
+ const DEFAULT_CLAUDE_SMART_HOST = HOST_CLAUDE_CODE;
54
63
  const REFLEXIO_DIR = join(homedir(), ".reflexio");
55
64
  const CLAUDE_SMART_STATE_DIR = join(homedir(), ".claude-smart");
65
+ const INSTALL_FAILURE_MARKER = join(CLAUDE_SMART_STATE_DIR, "install-failed");
56
66
  const OPENCODE_LOCAL_PACKAGE_DIR = join(CLAUDE_SMART_STATE_DIR, "opencode", "claude-smart");
57
67
  const OPENCODE_PACKAGE_LOCK_TIMEOUT_MS = 120_000;
58
68
  const OPENCODE_PACKAGE_LOCK_STALE_MS = 10 * 60_000;
@@ -96,7 +106,7 @@ const CODEX_REQUIRED_FILES = [
96
106
  "plugin/scripts/_codex_env.sh",
97
107
  ];
98
108
  const CODEX_CLI_TIMEOUT_MS = 30_000;
99
- const PLUGIN_SERVICE_TIMEOUT_MS = 15_000;
109
+ const PLUGIN_SERVICE_TIMEOUT_MS = 45_000;
100
110
  const COPYTREE_IGNORE_NAMES = new Set([
101
111
  "__pycache__",
102
112
  ".venv",
@@ -108,7 +118,7 @@ const COPYTREE_IGNORE_NAMES = new Set([
108
118
  ]);
109
119
  const LOCAL_DEFAULT_ENV_ENTRIES = [
110
120
  [
111
- "# Route reflexio generation through the local Claude Code CLI",
121
+ "# Route reflexio generation through the configured local host CLI",
112
122
  CLAUDE_SMART_USE_LOCAL_CLI_ENV,
113
123
  "1",
114
124
  ],
@@ -118,13 +128,17 @@ const LOCAL_DEFAULT_ENV_ENTRIES = [
118
128
  "1",
119
129
  ],
120
130
  [null, CLAUDE_SMART_READ_ONLY_ENV, "0"],
131
+ [null, CLAUDE_SMART_HOST_ENV, DEFAULT_CLAUDE_SMART_HOST],
121
132
  ];
133
+ const ENV_OVERRIDABLE_LOCAL_DEFAULT_KEYS = new Set([
134
+ CLAUDE_SMART_USE_LOCAL_CLI_ENV,
135
+ CLAUDE_SMART_USE_LOCAL_EMBEDDING_ENV,
136
+ ]);
122
137
  const LOCAL_MODE_PRUNE_KEYS = new Set([
123
138
  "REFLEXIO_URL",
124
139
  "REFLEXIO_API_KEY",
125
140
  REFLEXIO_USER_ID_ENV,
126
141
  ]);
127
-
128
142
  function shouldCopyPath(src) {
129
143
  const base = src.split(/[\\/]/).pop() || "";
130
144
  if (COPYTREE_IGNORE_NAMES.has(base)) return false;
@@ -254,14 +268,25 @@ function escapeEnvValue(value) {
254
268
  return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
255
269
  }
256
270
 
257
- function ensureLocalReflexioEnv() {
258
- mkdirSync(dirname(REFLEXIO_ENV_PATH), { recursive: true });
259
- const existing = existsSync(REFLEXIO_ENV_PATH)
260
- ? readFileSync(REFLEXIO_ENV_PATH, "utf8")
271
+ function resolveLocalEnvDefault(key, fallback, installHost) {
272
+ if (key === CLAUDE_SMART_HOST_ENV) return installHost;
273
+ if (ENV_OVERRIDABLE_LOCAL_DEFAULT_KEYS.has(key)) {
274
+ const explicit = (process.env[key] || "").trim();
275
+ if (explicit) return explicit;
276
+ }
277
+ return fallback;
278
+ }
279
+
280
+ function ensureLocalEnvFile(path, installHost) {
281
+ mkdirSync(dirname(path), { recursive: true });
282
+ const existing = existsSync(path)
283
+ ? readFileSync(path, "utf8")
261
284
  : "";
262
285
  const present = new Set();
263
286
  const keptLines = [];
264
287
  let pruned = false;
288
+ let changed = false;
289
+ let hostWritten = false;
265
290
  for (const line of existing.split(/\r?\n/)) {
266
291
  const parsed = parseEnvLine(line);
267
292
  if (parsed) {
@@ -269,6 +294,18 @@ function ensureLocalReflexioEnv() {
269
294
  pruned = true;
270
295
  continue;
271
296
  }
297
+ if (parsed.key === CLAUDE_SMART_HOST_ENV) {
298
+ present.add(parsed.key);
299
+ if (!hostWritten) {
300
+ const replacement = `${CLAUDE_SMART_HOST_ENV}=${escapeEnvValue(installHost)}`;
301
+ keptLines.push(replacement);
302
+ hostWritten = true;
303
+ if (line !== replacement || parsed.value !== installHost) changed = true;
304
+ } else {
305
+ changed = true;
306
+ }
307
+ continue;
308
+ }
272
309
  present.add(parsed.key);
273
310
  }
274
311
  keptLines.push(line);
@@ -278,29 +315,62 @@ function ensureLocalReflexioEnv() {
278
315
  const added = [];
279
316
  for (const [comment, key, value] of LOCAL_DEFAULT_ENV_ENTRIES) {
280
317
  if (present.has(key)) continue;
318
+ const effectiveValue = resolveLocalEnvDefault(key, value, installHost);
281
319
  if (comment) additions.push(comment);
282
320
  if (key === CLAUDE_SMART_READ_ONLY_ENV) {
283
- additions.push(`${key}="${escapeEnvValue(value)}"`);
321
+ additions.push(`${key}="${escapeEnvValue(effectiveValue)}"`);
284
322
  } else {
285
- additions.push(`${key}=${escapeEnvValue(value)}`);
323
+ additions.push(`${key}=${escapeEnvValue(effectiveValue)}`);
286
324
  }
287
325
  added.push(key);
288
326
  }
289
327
 
290
- if (additions.length > 0 || pruned) {
328
+ if (additions.length > 0 || pruned || changed) {
291
329
  let content = keptLines.join("\n").replace(/\n*$/, "");
292
330
  if (additions.length > 0) {
293
331
  const prefix = content ? "\n" : "";
294
332
  content = content + prefix + additions.join("\n");
295
333
  }
296
- writeFileSync(REFLEXIO_ENV_PATH, content ? `${content}\n` : "");
297
- } else if (!existsSync(REFLEXIO_ENV_PATH)) {
298
- writeFileSync(REFLEXIO_ENV_PATH, "");
334
+ writeFileSync(path, content ? `${content}\n` : "");
335
+ } else if (!existsSync(path)) {
336
+ writeFileSync(path, "");
337
+ }
338
+ chmodSync(path, 0o600);
339
+ return added;
340
+ }
341
+
342
+ function setEnvVars(path, values) {
343
+ mkdirSync(dirname(path), { recursive: true });
344
+ const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
345
+ const seen = new Set();
346
+ const out = [];
347
+ for (const line of existing ? existing.split(/\r?\n/) : []) {
348
+ const parsed = parseEnvLine(line);
349
+ if (!parsed || !(parsed.key in values)) {
350
+ out.push(line);
351
+ continue;
352
+ }
353
+ out.push(`${parsed.key}="${escapeEnvValue(values[parsed.key])}"`);
354
+ seen.add(parsed.key);
299
355
  }
300
- chmodSync(REFLEXIO_ENV_PATH, 0o600);
356
+ const added = [];
357
+ for (const [key, value] of Object.entries(values)) {
358
+ if (seen.has(key)) continue;
359
+ out.push(`${key}="${escapeEnvValue(value)}"`);
360
+ added.push(key);
361
+ }
362
+ const content = out.join("\n").replace(/\n*$/, "");
363
+ writeFileSync(path, content ? `${content}\n` : "");
364
+ chmodSync(path, 0o600);
301
365
  return added;
302
366
  }
303
367
 
368
+ function ensureLocalInstallEnvDefaults(installHost = DEFAULT_CLAUDE_SMART_HOST) {
369
+ const added = ensureLocalEnvFile(REFLEXIO_SETUP_ENV_PATH, installHost);
370
+ const runtimeAdded = ensureLocalEnvFile(CLAUDE_SMART_ENV_PATH, installHost);
371
+ return Array.from(new Set([...added, ...runtimeAdded]));
372
+ }
373
+
304
374
  function maskSecret(value) {
305
375
  if (!value) return "";
306
376
  if (value.length <= 8) return "*".repeat(value.length);
@@ -308,12 +378,12 @@ function maskSecret(value) {
308
378
  return `${prefix}****${value.slice(-4)}`;
309
379
  }
310
380
 
311
- function loadReflexioSetupEnv() {
381
+ function loadReflexioSetupEnv(installHost = DEFAULT_CLAUDE_SMART_HOST) {
312
382
  let readOnlyValue = "";
313
383
  let fileApiKey = "";
314
384
  let fileUrl = "";
315
- if (existsSync(REFLEXIO_ENV_PATH)) {
316
- const text = readFileSync(REFLEXIO_ENV_PATH, "utf8");
385
+ if (existsSync(REFLEXIO_SETUP_ENV_PATH)) {
386
+ const text = readFileSync(REFLEXIO_SETUP_ENV_PATH, "utf8");
317
387
  for (const line of text.split(/\r?\n/)) {
318
388
  const parsed = parseEnvLine(line);
319
389
  if (!parsed) continue;
@@ -341,9 +411,9 @@ function loadReflexioSetupEnv() {
341
411
  delete process.env.REFLEXIO_API_KEY;
342
412
  delete process.env[REFLEXIO_USER_ID_ENV];
343
413
  delete process.env[MANAGED_SETUP_ENV];
344
- const added = ensureLocalReflexioEnv();
414
+ const added = ensureLocalInstallEnvDefaults(installHost);
345
415
  if (added.length > 0) {
346
- process.stdout.write(`Seeded ${REFLEXIO_ENV_PATH} with ${added.join(", ")}.\n`);
416
+ process.stdout.write(`Seeded ${REFLEXIO_SETUP_ENV_PATH} with ${added.join(", ")}.\n`);
347
417
  }
348
418
  }
349
419
  const readOnly = ["1", "true", "yes", "on"].includes(
@@ -352,8 +422,8 @@ function loadReflexioSetupEnv() {
352
422
  return { readOnly };
353
423
  }
354
424
 
355
- function configureReflexioSetup() {
356
- return loadReflexioSetupEnv();
425
+ function configureReflexioSetup(installHost = DEFAULT_CLAUDE_SMART_HOST) {
426
+ return loadReflexioSetupEnv(installHost);
357
427
  }
358
428
 
359
429
  function stripJsonc(text) {
@@ -529,7 +599,7 @@ function hasExtractionProvider() {
529
599
  if ((process.env.REFLEXIO_API_KEY || "").trim()) return true;
530
600
  const cliPath = (process.env.CLAUDE_SMART_CLI_PATH || "").trim();
531
601
  if (cliPath && isExecutableFile(cliPath)) return true;
532
- return hasCli("claude") || hasCli("codex") || hasCli("opencode");
602
+ return hasCli("claude") || hasCli("codex") || Boolean(resolveOpenCodePath());
533
603
  }
534
604
 
535
605
  function isExecutableFile(path) {
@@ -550,6 +620,94 @@ function extractionProviderError() {
550
620
  );
551
621
  }
552
622
 
623
+ function hasOpenCodeCli() {
624
+ return Boolean(resolveOpenCodePath());
625
+ }
626
+
627
+ function resolveOpenCodePath() {
628
+ const opencodePath = (process.env[CLAUDE_SMART_OPENCODE_PATH_ENV] || "").trim();
629
+ if (opencodePath && isExecutableFile(opencodePath)) return opencodePath;
630
+ return resolveCommand(isWindows() ? ["opencode.cmd", "opencode.exe", "opencode"] : ["opencode"]);
631
+ }
632
+
633
+ function persistOpenCodePath() {
634
+ const resolved = resolveOpenCodePath();
635
+ if (!resolved) return [];
636
+ process.env[CLAUDE_SMART_OPENCODE_PATH_ENV] = resolved;
637
+ return setEnvVars(CLAUDE_SMART_ENV_PATH, { [CLAUDE_SMART_OPENCODE_PATH_ENV]: resolved });
638
+ }
639
+
640
+ const WINDOWS_SYSTEM_BASH_SUFFIXES = [
641
+ "\\windows\\system32\\bash.exe",
642
+ "\\windows\\sysnative\\bash.exe",
643
+ "\\windows\\syswow64\\bash.exe",
644
+ ];
645
+
646
+ function windowsPathText(path) {
647
+ return path.replace(/\//g, "\\").toLowerCase();
648
+ }
649
+
650
+ function isWindowsSystemBash(path) {
651
+ const normalized = windowsPathText(path);
652
+ return WINDOWS_SYSTEM_BASH_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
653
+ }
654
+
655
+ function pathCommandCandidates(names) {
656
+ // Return every PATH match so Windows can skip System32 bash and still find Git Bash.
657
+ const delimiter = isWindows() ? ";" : ":";
658
+ const pathParts = (process.env.PATH || "").split(delimiter).filter(Boolean);
659
+ const candidates = [];
660
+ for (const dir of pathParts) {
661
+ for (const name of names) {
662
+ const candidate = join(dir, name);
663
+ if (existsSync(candidate)) candidates.push(candidate);
664
+ }
665
+ }
666
+ return candidates;
667
+ }
668
+
669
+ function firstUsableBash(candidates) {
670
+ for (const candidate of candidates) {
671
+ const resolved = existsSync(candidate) ? candidate : resolveCommand([candidate]);
672
+ if (resolved && !isWindowsSystemBash(resolved)) return resolved;
673
+ }
674
+ return null;
675
+ }
676
+
677
+ function resolveUsableBash() {
678
+ if (!isWindows()) return resolveCommand(["bash"]);
679
+ const sources = [];
680
+ const bashEnv = (process.env.BASH || "").trim();
681
+ if (bashEnv) sources.push([bashEnv]);
682
+ sources.push([
683
+ "C:\\Program Files\\Git\\bin\\bash.exe",
684
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
685
+ ]);
686
+ sources.push(pathCommandCandidates(["bash.exe", "bash"]));
687
+ sources.push(["bash.exe", "bash"]);
688
+ for (const source of sources) {
689
+ const resolved = firstUsableBash(source);
690
+ if (resolved) return resolved;
691
+ }
692
+ return null;
693
+ }
694
+
695
+ function opencodePrerequisiteError() {
696
+ if (!hasOpenCodeCli()) {
697
+ return (
698
+ "error: OpenCode CLI not found on PATH. Install OpenCode first, " +
699
+ "or set CLAUDE_SMART_OPENCODE_PATH to the OpenCode executable.\n"
700
+ );
701
+ }
702
+ if (isWindows() && !resolveUsableBash()) {
703
+ return (
704
+ "error: Git Bash is required for claude-smart OpenCode support on Windows. " +
705
+ "Install Git for Windows and ensure bash.exe is on PATH, or run OpenCode from WSL.\n"
706
+ );
707
+ }
708
+ return null;
709
+ }
710
+
553
711
  function findClaudeCodePluginRoot() {
554
712
  const cacheRoot = join(homedir(), ".claude", "plugins", "cache", CODEX_MARKETPLACE_NAME, "claude-smart");
555
713
  const candidates = [];
@@ -658,6 +816,66 @@ function fileSha256(path) {
658
816
  return crypto.createHash("sha256").update(readFileSync(path)).digest("hex");
659
817
  }
660
818
 
819
+ function claudeCodeCacheRoot() {
820
+ return join(homedir(), ".claude", "plugins", "cache", CODEX_MARKETPLACE_NAME, "claude-smart");
821
+ }
822
+
823
+ // Version of the plugin this npm package ships. The Claude Code cache is keyed
824
+ // by this version, and each version vendors a different Reflexio — so the
825
+ // vendor payload may only ever be compared against the cache dir for *this*
826
+ // version. Comparing against some other version's cache reports a spurious
827
+ // mismatch (its vendor legitimately differs) and would force a pointless
828
+ // reinstall.
829
+ function claudeCodePackageVersion(sourceRoot = join(PACKAGE_ROOT, "plugin")) {
830
+ try {
831
+ const manifest = JSON.parse(
832
+ readFileSync(join(sourceRoot, ".claude-plugin", "plugin.json"), "utf8"),
833
+ );
834
+ if (typeof manifest.version === "string" && manifest.version) return manifest.version;
835
+ } catch {
836
+ // Fall through to pyproject.toml.
837
+ }
838
+ try {
839
+ const match = readFileSync(join(sourceRoot, "pyproject.toml"), "utf8").match(
840
+ /^version\s*=\s*"([^"]+)"/m,
841
+ );
842
+ if (match) return match[1];
843
+ } catch {
844
+ // No readable version.
845
+ }
846
+ return null;
847
+ }
848
+
849
+ function claudeCodeVendorPayloadMismatch(
850
+ pluginRoot,
851
+ sourceRoot = join(PACKAGE_ROOT, "plugin"),
852
+ ) {
853
+ // A sentinel pair, not a full tree hash: it catches the failure this exists
854
+ // for (a cache with no vendor bundle at all, or one from a different build),
855
+ // not a vendor tree that is individually corrupt past these two files.
856
+ const vendorFiles = [
857
+ "vendor/reflexio/pyproject.toml",
858
+ "vendor/reflexio/reflexio/__init__.py",
859
+ ];
860
+ // A git checkout gitignores the generated vendor bundle, so a source tree
861
+ // without one is a dev install, not a broken cache. Nothing to compare.
862
+ const sourceHasVendor = vendorFiles.some((rel) =>
863
+ existsSync(join(sourceRoot, rel)),
864
+ );
865
+ if (!sourceHasVendor) return null;
866
+
867
+ for (const rel of vendorFiles) {
868
+ const source = join(sourceRoot, rel);
869
+ const cached = join(pluginRoot, rel);
870
+ if (!existsSync(source)) {
871
+ throw new Error(`installed claude-smart package is missing ${rel}`);
872
+ }
873
+ if (!existsSync(cached)) return rel;
874
+ if (fileSha256(source) !== fileSha256(cached)) return rel;
875
+ }
876
+ return null;
877
+ }
878
+
661
879
  function verifyOpenCodePluginPackage(packageRoot) {
662
880
  const sourceScript = join(PACKAGE_ROOT, "plugin", "scripts", "smart-install.sh");
663
881
  const copiedScript = join(packageRoot, "plugin", "scripts", "smart-install.sh");
@@ -789,11 +1007,49 @@ async function bootstrapClaudeCodeInstall() {
789
1007
  if (code !== 0) {
790
1008
  throw new Error(`smart-install.sh failed in ${pluginRoot}`);
791
1009
  }
792
- const failureMarker = join(CLAUDE_SMART_STATE_DIR, "install-failed");
793
- if (existsSync(failureMarker)) {
794
- const reason = readFileSync(failureMarker, "utf8").trim() || "unknown error";
795
- throw new Error(reason);
1010
+ throwIfInstallFailureMarker();
1011
+ return pluginRoot;
1012
+ }
1013
+
1014
+ async function refreshClaudeCodeCacheIfVendorMissing() {
1015
+ const version = claudeCodePackageVersion();
1016
+ if (!version) return null;
1017
+ // Only ever inspect and repair the cache dir for the version we are
1018
+ // installing. Other version dirs coexist in the cache and are not ours to
1019
+ // judge — `findClaudeCodePluginRoot()` returns the highest one, which is a
1020
+ // different plugin build whenever this package is a downgrade.
1021
+ const pluginRoot = join(claudeCodeCacheRoot(), version);
1022
+ if (!existsSync(pluginRoot)) return null;
1023
+
1024
+ const mismatch = claudeCodeVendorPayloadMismatch(pluginRoot);
1025
+ if (!mismatch) return pluginRoot;
1026
+
1027
+ process.stderr.write(
1028
+ `warning: cached claude-smart plugin is missing or has stale ${mismatch}; reinstalling from the bundled npm package.\n`,
1029
+ );
1030
+ let code = await runClaude(["plugin", "uninstall", PLUGIN_SPEC], {
1031
+ spinnerLabel: "Removing stale claude-smart cache…",
1032
+ });
1033
+ if (code !== 0) {
1034
+ throw new Error(`could not remove stale ${PLUGIN_SPEC} cache (exit ${code})`);
1035
+ }
1036
+ code = await runClaude(["plugin", "install", PLUGIN_SPEC], {
1037
+ spinnerLabel: "Refreshing claude-smart cache…",
1038
+ });
1039
+ if (code !== 0) {
1040
+ throw new Error(`could not reinstall ${PLUGIN_SPEC} from the bundled package (exit ${code})`);
1041
+ }
1042
+
1043
+ if (!existsSync(pluginRoot)) {
1044
+ throw new Error(`Claude Code did not recreate the claude-smart plugin cache at ${pluginRoot}`);
1045
+ }
1046
+ const remainingMismatch = claudeCodeVendorPayloadMismatch(pluginRoot);
1047
+ if (remainingMismatch) {
1048
+ throw new Error(
1049
+ `refreshed claude-smart cache still does not match the bundled npm package: ${remainingMismatch}`,
1050
+ );
796
1051
  }
1052
+ process.stdout.write("Refreshed stale claude-smart plugin cache from the bundled npm package.\n");
797
1053
  return pluginRoot;
798
1054
  }
799
1055
 
@@ -870,8 +1126,14 @@ function runSilentStatus(command, args, options = {}) {
870
1126
  function runPluginService(pluginRoot, scriptName, subcommand, envOverrides = {}) {
871
1127
  const script = join(pluginRoot, "scripts", scriptName);
872
1128
  if (!existsSync(script)) return false;
873
- const bash = resolveCommand(isWindows() ? ["bash.exe", "bash"] : ["bash"]);
874
- if (!bash) return false;
1129
+ const bash = resolveUsableBash();
1130
+ if (!bash) {
1131
+ const reason = isWindows()
1132
+ ? "Git Bash is required for claude-smart services on Windows. Install Git for Windows and ensure bash.exe is on PATH, or run from WSL"
1133
+ : "bash is required but was not found on PATH";
1134
+ process.stderr.write(`warning: ${scriptName} ${subcommand} ${reason}; continuing.\n`);
1135
+ return false;
1136
+ }
875
1137
  const result = spawnSync(bash, [script, subcommand], {
876
1138
  cwd: pluginRoot,
877
1139
  env: { ...runtimeEnv(), ...envOverrides },
@@ -1224,24 +1486,84 @@ function patchCodexHooksForNode(pluginRoot, nodePath) {
1224
1486
  }
1225
1487
 
1226
1488
  function ensurePluginRoot(pluginRoot) {
1227
- const reflexioDir = dirname(REFLEXIO_ENV_PATH);
1228
- const link = join(reflexioDir, "plugin-root");
1489
+ const reflexioDir = dirname(REFLEXIO_SETUP_ENV_PATH);
1490
+ const pluginRootLink = join(reflexioDir, "plugin-root");
1229
1491
  mkdirSync(reflexioDir, { recursive: true });
1230
- rmSync(link, { recursive: true, force: true });
1492
+ let pathNotReplaceable = false;
1231
1493
  try {
1232
- require("fs").symlinkSync(pluginRoot, link, isWindows() ? "junction" : "dir");
1494
+ const existing = lstatSync(pluginRootLink);
1495
+ if (existing.isSymbolicLink() || existing.isFile()) {
1496
+ rmSync(pluginRootLink, { recursive: true, force: true });
1497
+ } else {
1498
+ pathNotReplaceable = true;
1499
+ }
1500
+ } catch (err) {
1501
+ if (!err || err.code !== "ENOENT") pathNotReplaceable = true;
1502
+ }
1503
+ if (pathNotReplaceable) {
1504
+ writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
1505
+ return;
1506
+ }
1507
+ try {
1508
+ symlinkSync(pluginRoot, pluginRootLink, isWindows() ? "junction" : "dir");
1509
+ writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
1233
1510
  } catch {
1234
1511
  writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
1235
1512
  }
1236
1513
  }
1237
1514
 
1515
+ function pluginPythonPath(pluginRoot) {
1516
+ // Python venvs created by uv use Scripts/python.exe on Windows across x64/arm64.
1517
+ return isWindows()
1518
+ ? join(pluginRoot, ".venv", "Scripts", "python.exe")
1519
+ : join(pluginRoot, ".venv", "bin", "python");
1520
+ }
1521
+
1522
+ function installFailureReason() {
1523
+ // smart-install.sh owns the install-failed marker format; Node surfaces the
1524
+ // first line as the actionable reason and leaves fingerprint handling to shell.
1525
+ const text = readFileSync(INSTALL_FAILURE_MARKER, "utf8");
1526
+ const first = text.split(/\r?\n/, 1)[0].trim();
1527
+ return first || "unknown error";
1528
+ }
1529
+
1530
+ function throwIfInstallFailureMarker() {
1531
+ if (existsSync(INSTALL_FAILURE_MARKER)) throw new Error(installFailureReason());
1532
+ }
1533
+
1534
+ function verifyWindowsLocalEmbeddingRuntime(pluginRoot, env) {
1535
+ if (!isWindows()) return;
1536
+ // Reflexio owns local embeddings, but this installer is the first place a
1537
+ // user sees whether the prepared Reflexio runtime can import onnxruntime.
1538
+ const script = join(pluginRoot, "scripts", "smart-install.sh");
1539
+ const bash = resolveUsableBash();
1540
+ if (!bash) {
1541
+ throw new Error(
1542
+ "Git Bash is required for claude-smart dependency checks on Windows. Install Git for Windows and ensure bash.exe is on PATH, or run from WSL.",
1543
+ );
1544
+ }
1545
+ const result = spawnSync(bash, [script, "verify-windows-embedding"], {
1546
+ cwd: pluginRoot,
1547
+ env,
1548
+ encoding: "utf8",
1549
+ windowsHide: true,
1550
+ });
1551
+ if (result.error || result.signal || result.status !== 0) {
1552
+ const detail = result.error
1553
+ ? result.error.message
1554
+ : result.signal
1555
+ ? `terminated by ${result.signal}`
1556
+ : (result.stderr || "").trim() || `exited with status ${result.status}`;
1557
+ throw new Error(`Windows local embedding preflight failed: ${detail}`);
1558
+ }
1559
+ throwIfInstallFailureMarker();
1560
+ }
1561
+
1238
1562
  async function installVendoredReflexio(pluginRoot, uv, env) {
1239
1563
  const vendorRoot = join(pluginRoot, "vendor", "reflexio");
1240
1564
  if (!existsSync(join(vendorRoot, "pyproject.toml"))) return;
1241
1565
 
1242
- const pythonPath = isWindows()
1243
- ? join(pluginRoot, ".venv", "Scripts", "python.exe")
1244
- : join(pluginRoot, ".venv", "bin", "python");
1566
+ const pythonPath = pluginPythonPath(pluginRoot);
1245
1567
  if (!existsSync(pythonPath)) {
1246
1568
  throw new Error(`plugin Python was not created by uv sync: ${pythonPath}`);
1247
1569
  }
@@ -1321,6 +1643,7 @@ async function syncPluginPythonEnv(pluginRoot, uv, env) {
1321
1643
  async function bootstrapPluginRuntime(pluginRoot, options = {}) {
1322
1644
  assertSupportedRuntimePlatform();
1323
1645
  process.stdout.write("Preparing claude-smart runtime for hooks...\n");
1646
+ rmSync(INSTALL_FAILURE_MARKER, { force: true });
1324
1647
  const nodeRuntime = await ensurePrivateNode();
1325
1648
  if (options.patchCodexHooks !== false) {
1326
1649
  patchCodexHooksForNode(pluginRoot, nodeRuntime.node);
@@ -1341,6 +1664,7 @@ async function bootstrapPluginRuntime(pluginRoot, options = {}) {
1341
1664
  }
1342
1665
  await syncPluginPythonEnv(pluginRoot, uv, env);
1343
1666
  await installVendoredReflexio(pluginRoot, uv, env);
1667
+ verifyWindowsLocalEmbeddingRuntime(pluginRoot, env);
1344
1668
 
1345
1669
  const dashboardDir = join(pluginRoot, "dashboard");
1346
1670
  if (existsSync(dashboardDir)) {
@@ -1368,7 +1692,7 @@ function printHelp() {
1368
1692
  "Claude Code install:",
1369
1693
  " 1. claude plugin marketplace add <this package>",
1370
1694
  ` 2. claude plugin install ${PLUGIN_SPEC}`,
1371
- " 3. Reads ~/.reflexio/.env when managed/read-only setup was configured.",
1695
+ " 3. Reads setup/bootstrap config when managed/read-only setup was configured.",
1372
1696
  "",
1373
1697
  "Codex install:",
1374
1698
  ` 1. Copies the bundled marketplace to ${CODEX_MARKETPLACE_DIR}`,
@@ -1400,14 +1724,14 @@ function printHelp() {
1400
1724
 
1401
1725
  function parseHost(args) {
1402
1726
  const idx = args.indexOf("--host");
1403
- if (idx === -1) return "claude-code";
1727
+ if (idx === -1) return DEFAULT_CLAUDE_SMART_HOST;
1404
1728
  const value = args[idx + 1];
1405
1729
  if (!value) {
1406
- process.stderr.write("error: --host requires a value: claude-code, codex, or opencode\n");
1730
+ process.stderr.write(`error: --host requires a value: ${SUPPORTED_HOSTS.join(", ")}\n`);
1407
1731
  process.exit(1);
1408
1732
  }
1409
- if (value !== "claude-code" && value !== "codex" && value !== "opencode") {
1410
- process.stderr.write("error: --host must be claude-code, codex, or opencode\n");
1733
+ if (!SUPPORTED_HOSTS.includes(value)) {
1734
+ process.stderr.write(`error: --host must be ${SUPPORTED_HOSTS.join(", ")}\n`);
1411
1735
  process.exit(1);
1412
1736
  }
1413
1737
  return value;
@@ -1822,11 +2146,11 @@ function findCodexPluginRoot() {
1822
2146
  }
1823
2147
 
1824
2148
  async function runUpdate(args) {
1825
- if (parseHost(args) === "codex") {
2149
+ if (parseHost(args) === HOST_CODEX) {
1826
2150
  await runUpdateCodex(args);
1827
2151
  return;
1828
2152
  }
1829
- if (parseHost(args) === "opencode") {
2153
+ if (parseHost(args) === HOST_OPENCODE) {
1830
2154
  await runUpdateOpenCode(args);
1831
2155
  return;
1832
2156
  }
@@ -1854,11 +2178,11 @@ async function runUpdateOpenCode(args) {
1854
2178
  }
1855
2179
 
1856
2180
  async function runUninstall(args) {
1857
- if (parseHost(args) === "codex") {
2181
+ if (parseHost(args) === HOST_CODEX) {
1858
2182
  await runUninstallCodex();
1859
2183
  return;
1860
2184
  }
1861
- if (parseHost(args) === "opencode") {
2185
+ if (parseHost(args) === HOST_OPENCODE) {
1862
2186
  await runUninstallOpenCode(args);
1863
2187
  return;
1864
2188
  }
@@ -1908,11 +2232,11 @@ async function runSetup(args) {
1908
2232
  }
1909
2233
 
1910
2234
  async function runInstall(args, options = {}) {
1911
- if (parseHost(args) === "codex") {
2235
+ if (parseHost(args) === HOST_CODEX) {
1912
2236
  await runInstallCodex(args);
1913
2237
  return;
1914
2238
  }
1915
- if (parseHost(args) === "opencode") {
2239
+ if (parseHost(args) === HOST_OPENCODE) {
1916
2240
  await runInstallOpenCode(args);
1917
2241
  return;
1918
2242
  }
@@ -1926,7 +2250,7 @@ async function runInstall(args, options = {}) {
1926
2250
  }
1927
2251
 
1928
2252
  const source = PACKAGE_ROOT;
1929
- const setup = configureReflexioSetup();
2253
+ const setup = configureReflexioSetup(HOST_CLAUDE_CODE);
1930
2254
  const readOnly = setup.readOnly;
1931
2255
 
1932
2256
  const steps = [
@@ -1959,6 +2283,7 @@ async function runInstall(args, options = {}) {
1959
2283
  }
1960
2284
 
1961
2285
  try {
2286
+ await refreshClaudeCodeCacheIfVendorMissing();
1962
2287
  const pluginRoot = await bootstrapClaudeCodeInstall();
1963
2288
  restorePublishHooksFromSource(pluginRoot);
1964
2289
  if (readOnly) {
@@ -1966,7 +2291,7 @@ async function runInstall(args, options = {}) {
1966
2291
  process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
1967
2292
  }
1968
2293
  process.stdout.write(`Prepared claude-smart runtime at ${pluginRoot}.\n`);
1969
- if (startBackendService(pluginRoot, "claude-code")) {
2294
+ if (startBackendService(pluginRoot, HOST_CLAUDE_CODE)) {
1970
2295
  process.stdout.write("Started claude-smart backend service.\n");
1971
2296
  }
1972
2297
  if (refreshDashboardService(pluginRoot)) {
@@ -1998,7 +2323,7 @@ async function runInstallCodex(args) {
1998
2323
  process.stderr.write("error: 'codex' CLI not found on PATH. Install Codex first.\n");
1999
2324
  process.exit(1);
2000
2325
  }
2001
- const setup = configureReflexioSetup();
2326
+ const setup = configureReflexioSetup(HOST_CODEX);
2002
2327
  const readOnly = setup.readOnly;
2003
2328
 
2004
2329
  const marketplaceRoot = copyCodexMarketplace();
@@ -2049,7 +2374,7 @@ async function runInstallCodex(args) {
2049
2374
  if (readOnly) {
2050
2375
  process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
2051
2376
  }
2052
- if (startBackendService(cacheDir, "codex")) {
2377
+ if (startBackendService(cacheDir, HOST_CODEX)) {
2053
2378
  process.stdout.write("Started claude-smart backend service.\n");
2054
2379
  }
2055
2380
  if (refreshDashboardService(cacheDir)) {
@@ -2099,8 +2424,14 @@ async function runInstallCodex(args) {
2099
2424
  }
2100
2425
 
2101
2426
  async function runInstallOpenCode(args) {
2102
- const setup = configureReflexioSetup();
2427
+ const prerequisiteError = opencodePrerequisiteError();
2428
+ if (prerequisiteError) {
2429
+ process.stderr.write(prerequisiteError);
2430
+ process.exit(1);
2431
+ }
2432
+ const setup = configureReflexioSetup(HOST_OPENCODE);
2103
2433
  const readOnly = setup.readOnly;
2434
+ persistOpenCodePath();
2104
2435
  if (!hasExtractionProvider()) {
2105
2436
  process.stderr.write(extractionProviderError());
2106
2437
  process.exit(1);
@@ -2139,7 +2470,7 @@ async function runInstallOpenCode(args) {
2139
2470
  if (readOnly) {
2140
2471
  process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
2141
2472
  }
2142
- if (startBackendService(pluginRoot, "opencode")) {
2473
+ if (startBackendService(pluginRoot, HOST_OPENCODE)) {
2143
2474
  process.stdout.write("Started claude-smart backend service.\n");
2144
2475
  }
2145
2476
  if (refreshDashboardService(pluginRoot)) {
@@ -2264,6 +2595,8 @@ if (require.main === module) {
2264
2595
  module.exports = {
2265
2596
  assertSupportedRuntimePlatform,
2266
2597
  bootstrapPluginRuntime,
2598
+ claudeCodePackageVersion,
2599
+ claudeCodeVendorPayloadMismatch,
2267
2600
  codexMarketplacePluginRoot,
2268
2601
  copyCodexMarketplace,
2269
2602
  ensurePrivateNode,
@@ -2274,7 +2607,12 @@ module.exports = {
2274
2607
  opencodeLocalPluginSpec,
2275
2608
  installOpenCodePluginPackage,
2276
2609
  patchOpenCodePluginConfig,
2610
+ parseHost,
2277
2611
  hasExtractionProvider,
2612
+ hasOpenCodeCli,
2613
+ persistOpenCodePath,
2614
+ resolveOpenCodePath,
2615
+ opencodePrerequisiteError,
2278
2616
  platformSupportError,
2279
2617
  prunePublishHooksForReadOnly,
2280
2618
  restorePublishHooksFromSource,