claude-smart 0.2.47 → 0.2.49

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 (139) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +6 -2
  3. package/bin/claude-smart.js +289 -56
  4. package/package.json +1 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
  8. package/plugin/dashboard/package-lock.json +61 -390
  9. package/plugin/dashboard/package.json +2 -2
  10. package/plugin/opencode/dist/server.mjs +60 -2
  11. package/plugin/opencode/server.mts +64 -2
  12. package/plugin/pyproject.toml +2 -2
  13. package/plugin/scripts/_lib.sh +58 -6
  14. package/plugin/scripts/backend-service.sh +15 -10
  15. package/plugin/scripts/codex-hook.js +16 -4
  16. package/plugin/scripts/dashboard-service.sh +1 -1
  17. package/plugin/scripts/ensure-plugin-root.sh +106 -8
  18. package/plugin/scripts/opencode-claude-compat.js +8 -1
  19. package/plugin/scripts/smart-install.sh +101 -20
  20. package/plugin/src/README.md +1 -1
  21. package/plugin/src/claude_smart/cli.py +79 -29
  22. package/plugin/src/claude_smart/env_config.py +53 -11
  23. package/plugin/uv.lock +5 -5
  24. package/plugin/vendor/reflexio/.env.example +7 -0
  25. package/plugin/vendor/reflexio/pyproject.toml +2 -1
  26. package/plugin/vendor/reflexio/reflexio/README.md +8 -5
  27. package/plugin/vendor/reflexio/reflexio/cli/README.md +3 -0
  28. package/plugin/vendor/reflexio/reflexio/client/client.py +40 -6
  29. package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
  30. package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
  31. package/plugin/vendor/reflexio/reflexio/lib/_search.py +15 -11
  32. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +18 -0
  33. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
  34. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +24 -3
  35. package/plugin/vendor/reflexio/reflexio/server/README.md +25 -8
  36. package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
  37. package/plugin/vendor/reflexio/reflexio/server/api.py +148 -3277
  38. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
  39. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
  40. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
  41. package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
  42. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
  43. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
  44. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
  45. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
  46. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
  47. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
  48. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +72 -1817
  49. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +56 -4
  50. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +183 -1
  51. package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
  52. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
  53. package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
  54. package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
  55. package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
  56. package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
  57. package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
  58. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
  59. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
  60. package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +320 -0
  61. package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
  62. package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
  63. package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
  64. package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
  65. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
  66. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
  67. package/plugin/vendor/reflexio/reflexio/server/services/README.md +3 -2
  68. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
  69. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
  70. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
  71. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
  72. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
  73. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
  74. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
  75. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
  76. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
  77. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/__init__.py +13 -0
  78. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
  79. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +193 -0
  80. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +24 -41
  81. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +335 -113
  82. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +362 -81
  83. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -490
  84. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
  85. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
  86. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
  87. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
  88. package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +44 -22
  89. package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
  90. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +31 -4
  91. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
  92. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +196 -353
  93. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +0 -1513
  94. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +379 -0
  95. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +17 -6
  96. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1281
  97. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
  98. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
  99. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
  100. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
  101. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
  102. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
  103. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
  104. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
  105. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +216 -0
  106. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
  107. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
  108. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
  109. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
  110. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
  111. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
  112. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +22 -10
  113. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +8 -3
  114. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +25 -12
  115. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
  116. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +308 -0
  117. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +920 -0
  118. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -8
  120. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
  121. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_commit_scope.py +9 -0
  122. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +219 -0
  123. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
  124. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
  125. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
  126. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
  127. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
  128. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
  129. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
  130. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
  131. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
  132. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
  133. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
  134. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
  135. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
  136. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +95 -0
  137. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +60 -80
  138. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
  139. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +0 -148
@@ -9,7 +9,7 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "claude-smart",
12
- "version": "0.2.47",
12
+ "version": "0.2.49",
13
13
  "source": "./plugin",
14
14
  "description": "Turns corrections into Preferences, Project-specific skills, and Shared skills — uses Claude Code, Codex, or OpenCode as the LLM backend (no external API key required)"
15
15
  }
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.47-green.svg" alt="Version">
16
+ <img src="https://img.shields.io/badge/version-0.2.49-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">
@@ -113,8 +113,12 @@ npx claude-smart install --host opencode
113
113
 
114
114
  Then restart OpenCode in your project so it loads the plugin from `opencode.json`, the documented project config file. If that project does not already have a root config but does have `.opencode/opencode.json` or `.opencode/opencode.jsonc`, the installer updates that existing file instead of creating a second config. If both locations exist, the root config wins. Use `--global` to install into `~/.config/opencode/opencode.json` for all OpenCode projects on this machine. The installer copies the active package to `~/.claude-smart/opencode/claude-smart`, prepares that runtime immediately, and writes a `file://` plugin entry so OpenCode reloads the same prepared package after restart.
115
115
 
116
+ The installer prepares claude-smart's runtime dependencies, but expects the OpenCode CLI to already be installed. It resolves and records the OpenCode executable in `~/.claude-smart/.env` for later backend restarts; set `CLAUDE_SMART_OPENCODE_PATH` before install if `opencode` is not on `PATH`.
117
+
116
118
  OpenCode support is new and uses OpenCode's plugin loader to inject relevant learned context before each model request. Learning extraction runs `opencode run --pure` from an isolated temp project, so it uses OpenCode's default model unless you set `CLAUDE_SMART_OPENCODE_MODEL=provider/model`. Set that env var if your normal project config pins a different provider or model.
117
119
 
120
+ On native Windows, install Git for Windows and make `bash.exe` available on `PATH` before starting OpenCode, or run OpenCode from WSL. The installer also checks the Windows local embedding runtime and writes actionable setup failures to `~/.claude-smart/install-failed`.
121
+
118
122
  To uninstall:
119
123
 
120
124
  ```bash
@@ -237,7 +241,7 @@ Advanced users can tune claude-smart via environment variables — see [DEVELOPE
237
241
  | Path | What |
238
242
  | --- | --- |
239
243
  | `~/.reflexio/data/reflexio.db` | Source of truth for learned preferences, skills, interactions, full-text indexes, and embedding tables (plus `.db-shm` / `.db-wal` WAL sidecars). Inspect with `sqlite3`. |
240
- | `~/.reflexio/.env` | Provider config — `CLAUDE_SMART_USE_LOCAL_CLI`, `CLAUDE_SMART_USE_LOCAL_EMBEDDING`, any optional API keys. |
244
+ | `~/.claude-smart/.env` | claude-smart local runtime config — `CLAUDE_SMART_HOST`, `CLAUDE_SMART_USE_LOCAL_CLI`, `CLAUDE_SMART_USE_LOCAL_EMBEDDING`, and `CLAUDE_SMART_OPENCODE_PATH`. |
241
245
  | `.claude/settings.local.json` or `~/.claude/settings.json` | Claude Code hook environment, such as `CLAUDE_SMART_ENABLE_OPTIMIZER`; use project-local settings for one repo or user settings for all projects. |
242
246
  | `~/.codex/config.toml` | Codex plugin state, hook feature flags, and per-hook trust entries after `claude-smart install --host codex`. |
243
247
  | `~/.codex/plugins/cache/reflexioai/claude-smart/<version>/` | Codex's cached install of the `claude-smart` plugin from the `ReflexioAI` marketplace. |
@@ -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, "");
299
337
  }
300
- chmodSync(REFLEXIO_ENV_PATH, 0o600);
338
+ chmodSync(path, 0o600);
301
339
  return added;
302
340
  }
303
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);
355
+ }
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);
365
+ return added;
366
+ }
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 = [];
@@ -789,11 +947,7 @@ async function bootstrapClaudeCodeInstall() {
789
947
  if (code !== 0) {
790
948
  throw new Error(`smart-install.sh failed in ${pluginRoot}`);
791
949
  }
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);
796
- }
950
+ throwIfInstallFailureMarker();
797
951
  return pluginRoot;
798
952
  }
799
953
 
@@ -870,8 +1024,14 @@ function runSilentStatus(command, args, options = {}) {
870
1024
  function runPluginService(pluginRoot, scriptName, subcommand, envOverrides = {}) {
871
1025
  const script = join(pluginRoot, "scripts", scriptName);
872
1026
  if (!existsSync(script)) return false;
873
- const bash = resolveCommand(isWindows() ? ["bash.exe", "bash"] : ["bash"]);
874
- if (!bash) return false;
1027
+ const bash = resolveUsableBash();
1028
+ if (!bash) {
1029
+ const reason = isWindows()
1030
+ ? "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"
1031
+ : "bash is required but was not found on PATH";
1032
+ process.stderr.write(`warning: ${scriptName} ${subcommand} ${reason}; continuing.\n`);
1033
+ return false;
1034
+ }
875
1035
  const result = spawnSync(bash, [script, subcommand], {
876
1036
  cwd: pluginRoot,
877
1037
  env: { ...runtimeEnv(), ...envOverrides },
@@ -1224,24 +1384,84 @@ function patchCodexHooksForNode(pluginRoot, nodePath) {
1224
1384
  }
1225
1385
 
1226
1386
  function ensurePluginRoot(pluginRoot) {
1227
- const reflexioDir = dirname(REFLEXIO_ENV_PATH);
1228
- const link = join(reflexioDir, "plugin-root");
1387
+ const reflexioDir = dirname(REFLEXIO_SETUP_ENV_PATH);
1388
+ const pluginRootLink = join(reflexioDir, "plugin-root");
1229
1389
  mkdirSync(reflexioDir, { recursive: true });
1230
- rmSync(link, { recursive: true, force: true });
1390
+ let pathNotReplaceable = false;
1231
1391
  try {
1232
- require("fs").symlinkSync(pluginRoot, link, isWindows() ? "junction" : "dir");
1392
+ const existing = lstatSync(pluginRootLink);
1393
+ if (existing.isSymbolicLink() || existing.isFile()) {
1394
+ rmSync(pluginRootLink, { recursive: true, force: true });
1395
+ } else {
1396
+ pathNotReplaceable = true;
1397
+ }
1398
+ } catch (err) {
1399
+ if (!err || err.code !== "ENOENT") pathNotReplaceable = true;
1400
+ }
1401
+ if (pathNotReplaceable) {
1402
+ writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
1403
+ return;
1404
+ }
1405
+ try {
1406
+ symlinkSync(pluginRoot, pluginRootLink, isWindows() ? "junction" : "dir");
1407
+ writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
1233
1408
  } catch {
1234
1409
  writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
1235
1410
  }
1236
1411
  }
1237
1412
 
1413
+ function pluginPythonPath(pluginRoot) {
1414
+ // Python venvs created by uv use Scripts/python.exe on Windows across x64/arm64.
1415
+ return isWindows()
1416
+ ? join(pluginRoot, ".venv", "Scripts", "python.exe")
1417
+ : join(pluginRoot, ".venv", "bin", "python");
1418
+ }
1419
+
1420
+ function installFailureReason() {
1421
+ // smart-install.sh owns the install-failed marker format; Node surfaces the
1422
+ // first line as the actionable reason and leaves fingerprint handling to shell.
1423
+ const text = readFileSync(INSTALL_FAILURE_MARKER, "utf8");
1424
+ const first = text.split(/\r?\n/, 1)[0].trim();
1425
+ return first || "unknown error";
1426
+ }
1427
+
1428
+ function throwIfInstallFailureMarker() {
1429
+ if (existsSync(INSTALL_FAILURE_MARKER)) throw new Error(installFailureReason());
1430
+ }
1431
+
1432
+ function verifyWindowsLocalEmbeddingRuntime(pluginRoot, env) {
1433
+ if (!isWindows()) return;
1434
+ // Reflexio owns local embeddings, but this installer is the first place a
1435
+ // user sees whether the prepared Reflexio runtime can import onnxruntime.
1436
+ const script = join(pluginRoot, "scripts", "smart-install.sh");
1437
+ const bash = resolveUsableBash();
1438
+ if (!bash) {
1439
+ throw new Error(
1440
+ "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.",
1441
+ );
1442
+ }
1443
+ const result = spawnSync(bash, [script, "verify-windows-embedding"], {
1444
+ cwd: pluginRoot,
1445
+ env,
1446
+ encoding: "utf8",
1447
+ windowsHide: true,
1448
+ });
1449
+ if (result.error || result.signal || result.status !== 0) {
1450
+ const detail = result.error
1451
+ ? result.error.message
1452
+ : result.signal
1453
+ ? `terminated by ${result.signal}`
1454
+ : (result.stderr || "").trim() || `exited with status ${result.status}`;
1455
+ throw new Error(`Windows local embedding preflight failed: ${detail}`);
1456
+ }
1457
+ throwIfInstallFailureMarker();
1458
+ }
1459
+
1238
1460
  async function installVendoredReflexio(pluginRoot, uv, env) {
1239
1461
  const vendorRoot = join(pluginRoot, "vendor", "reflexio");
1240
1462
  if (!existsSync(join(vendorRoot, "pyproject.toml"))) return;
1241
1463
 
1242
- const pythonPath = isWindows()
1243
- ? join(pluginRoot, ".venv", "Scripts", "python.exe")
1244
- : join(pluginRoot, ".venv", "bin", "python");
1464
+ const pythonPath = pluginPythonPath(pluginRoot);
1245
1465
  if (!existsSync(pythonPath)) {
1246
1466
  throw new Error(`plugin Python was not created by uv sync: ${pythonPath}`);
1247
1467
  }
@@ -1321,6 +1541,7 @@ async function syncPluginPythonEnv(pluginRoot, uv, env) {
1321
1541
  async function bootstrapPluginRuntime(pluginRoot, options = {}) {
1322
1542
  assertSupportedRuntimePlatform();
1323
1543
  process.stdout.write("Preparing claude-smart runtime for hooks...\n");
1544
+ rmSync(INSTALL_FAILURE_MARKER, { force: true });
1324
1545
  const nodeRuntime = await ensurePrivateNode();
1325
1546
  if (options.patchCodexHooks !== false) {
1326
1547
  patchCodexHooksForNode(pluginRoot, nodeRuntime.node);
@@ -1341,6 +1562,7 @@ async function bootstrapPluginRuntime(pluginRoot, options = {}) {
1341
1562
  }
1342
1563
  await syncPluginPythonEnv(pluginRoot, uv, env);
1343
1564
  await installVendoredReflexio(pluginRoot, uv, env);
1565
+ verifyWindowsLocalEmbeddingRuntime(pluginRoot, env);
1344
1566
 
1345
1567
  const dashboardDir = join(pluginRoot, "dashboard");
1346
1568
  if (existsSync(dashboardDir)) {
@@ -1368,7 +1590,7 @@ function printHelp() {
1368
1590
  "Claude Code install:",
1369
1591
  " 1. claude plugin marketplace add <this package>",
1370
1592
  ` 2. claude plugin install ${PLUGIN_SPEC}`,
1371
- " 3. Reads ~/.reflexio/.env when managed/read-only setup was configured.",
1593
+ " 3. Reads setup/bootstrap config when managed/read-only setup was configured.",
1372
1594
  "",
1373
1595
  "Codex install:",
1374
1596
  ` 1. Copies the bundled marketplace to ${CODEX_MARKETPLACE_DIR}`,
@@ -1400,14 +1622,14 @@ function printHelp() {
1400
1622
 
1401
1623
  function parseHost(args) {
1402
1624
  const idx = args.indexOf("--host");
1403
- if (idx === -1) return "claude-code";
1625
+ if (idx === -1) return DEFAULT_CLAUDE_SMART_HOST;
1404
1626
  const value = args[idx + 1];
1405
1627
  if (!value) {
1406
- process.stderr.write("error: --host requires a value: claude-code, codex, or opencode\n");
1628
+ process.stderr.write(`error: --host requires a value: ${SUPPORTED_HOSTS.join(", ")}\n`);
1407
1629
  process.exit(1);
1408
1630
  }
1409
- if (value !== "claude-code" && value !== "codex" && value !== "opencode") {
1410
- process.stderr.write("error: --host must be claude-code, codex, or opencode\n");
1631
+ if (!SUPPORTED_HOSTS.includes(value)) {
1632
+ process.stderr.write(`error: --host must be ${SUPPORTED_HOSTS.join(", ")}\n`);
1411
1633
  process.exit(1);
1412
1634
  }
1413
1635
  return value;
@@ -1822,11 +2044,11 @@ function findCodexPluginRoot() {
1822
2044
  }
1823
2045
 
1824
2046
  async function runUpdate(args) {
1825
- if (parseHost(args) === "codex") {
2047
+ if (parseHost(args) === HOST_CODEX) {
1826
2048
  await runUpdateCodex(args);
1827
2049
  return;
1828
2050
  }
1829
- if (parseHost(args) === "opencode") {
2051
+ if (parseHost(args) === HOST_OPENCODE) {
1830
2052
  await runUpdateOpenCode(args);
1831
2053
  return;
1832
2054
  }
@@ -1854,11 +2076,11 @@ async function runUpdateOpenCode(args) {
1854
2076
  }
1855
2077
 
1856
2078
  async function runUninstall(args) {
1857
- if (parseHost(args) === "codex") {
2079
+ if (parseHost(args) === HOST_CODEX) {
1858
2080
  await runUninstallCodex();
1859
2081
  return;
1860
2082
  }
1861
- if (parseHost(args) === "opencode") {
2083
+ if (parseHost(args) === HOST_OPENCODE) {
1862
2084
  await runUninstallOpenCode(args);
1863
2085
  return;
1864
2086
  }
@@ -1908,11 +2130,11 @@ async function runSetup(args) {
1908
2130
  }
1909
2131
 
1910
2132
  async function runInstall(args, options = {}) {
1911
- if (parseHost(args) === "codex") {
2133
+ if (parseHost(args) === HOST_CODEX) {
1912
2134
  await runInstallCodex(args);
1913
2135
  return;
1914
2136
  }
1915
- if (parseHost(args) === "opencode") {
2137
+ if (parseHost(args) === HOST_OPENCODE) {
1916
2138
  await runInstallOpenCode(args);
1917
2139
  return;
1918
2140
  }
@@ -1926,7 +2148,7 @@ async function runInstall(args, options = {}) {
1926
2148
  }
1927
2149
 
1928
2150
  const source = PACKAGE_ROOT;
1929
- const setup = configureReflexioSetup();
2151
+ const setup = configureReflexioSetup(HOST_CLAUDE_CODE);
1930
2152
  const readOnly = setup.readOnly;
1931
2153
 
1932
2154
  const steps = [
@@ -1966,7 +2188,7 @@ async function runInstall(args, options = {}) {
1966
2188
  process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
1967
2189
  }
1968
2190
  process.stdout.write(`Prepared claude-smart runtime at ${pluginRoot}.\n`);
1969
- if (startBackendService(pluginRoot, "claude-code")) {
2191
+ if (startBackendService(pluginRoot, HOST_CLAUDE_CODE)) {
1970
2192
  process.stdout.write("Started claude-smart backend service.\n");
1971
2193
  }
1972
2194
  if (refreshDashboardService(pluginRoot)) {
@@ -1998,7 +2220,7 @@ async function runInstallCodex(args) {
1998
2220
  process.stderr.write("error: 'codex' CLI not found on PATH. Install Codex first.\n");
1999
2221
  process.exit(1);
2000
2222
  }
2001
- const setup = configureReflexioSetup();
2223
+ const setup = configureReflexioSetup(HOST_CODEX);
2002
2224
  const readOnly = setup.readOnly;
2003
2225
 
2004
2226
  const marketplaceRoot = copyCodexMarketplace();
@@ -2049,7 +2271,7 @@ async function runInstallCodex(args) {
2049
2271
  if (readOnly) {
2050
2272
  process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
2051
2273
  }
2052
- if (startBackendService(cacheDir, "codex")) {
2274
+ if (startBackendService(cacheDir, HOST_CODEX)) {
2053
2275
  process.stdout.write("Started claude-smart backend service.\n");
2054
2276
  }
2055
2277
  if (refreshDashboardService(cacheDir)) {
@@ -2099,8 +2321,14 @@ async function runInstallCodex(args) {
2099
2321
  }
2100
2322
 
2101
2323
  async function runInstallOpenCode(args) {
2102
- const setup = configureReflexioSetup();
2324
+ const prerequisiteError = opencodePrerequisiteError();
2325
+ if (prerequisiteError) {
2326
+ process.stderr.write(prerequisiteError);
2327
+ process.exit(1);
2328
+ }
2329
+ const setup = configureReflexioSetup(HOST_OPENCODE);
2103
2330
  const readOnly = setup.readOnly;
2331
+ persistOpenCodePath();
2104
2332
  if (!hasExtractionProvider()) {
2105
2333
  process.stderr.write(extractionProviderError());
2106
2334
  process.exit(1);
@@ -2139,7 +2367,7 @@ async function runInstallOpenCode(args) {
2139
2367
  if (readOnly) {
2140
2368
  process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
2141
2369
  }
2142
- if (startBackendService(pluginRoot, "opencode")) {
2370
+ if (startBackendService(pluginRoot, HOST_OPENCODE)) {
2143
2371
  process.stdout.write("Started claude-smart backend service.\n");
2144
2372
  }
2145
2373
  if (refreshDashboardService(pluginRoot)) {
@@ -2274,7 +2502,12 @@ module.exports = {
2274
2502
  opencodeLocalPluginSpec,
2275
2503
  installOpenCodePluginPackage,
2276
2504
  patchOpenCodePluginConfig,
2505
+ parseHost,
2277
2506
  hasExtractionProvider,
2507
+ hasOpenCodeCli,
2508
+ persistOpenCodePath,
2509
+ resolveOpenCodePath,
2510
+ opencodePrerequisiteError,
2278
2511
  platformSupportError,
2279
2512
  prunePublishHooksForReadOnly,
2280
2513
  restorePublishHooksFromSource,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.47",
3
+ "version": "0.2.49",
4
4
  "description": "Self-improving Claude Code, Codex, and OpenCode plugin that turns corrections into Preferences, Project-specific skills, and Shared skills",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.47",
3
+ "version": "0.2.49",
4
4
  "description": "Self-improving Claude Code, Codex, and OpenCode plugin that turns corrections into Preferences, Project-specific skills, and Shared skills",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.47",
3
+ "version": "0.2.49",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"