claude-smart 0.2.49 → 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 (177) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +10 -43
  3. package/bin/claude-smart.js +105 -0
  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/pyproject.toml +1 -1
  27. package/plugin/scripts/_lib.sh +197 -1
  28. package/plugin/scripts/backend-python-runner.py +46 -0
  29. package/plugin/scripts/backend-service.sh +757 -119
  30. package/plugin/scripts/codex-hook.js +63 -225
  31. package/plugin/scripts/dashboard-open.sh +6 -4
  32. package/plugin/scripts/dashboard-service.sh +117 -136
  33. package/plugin/scripts/hook_entry.sh +3 -0
  34. package/plugin/scripts/smart-install.sh +15 -1
  35. package/plugin/src/claude_smart/cli.py +14 -0
  36. package/plugin/src/claude_smart/context_inject.py +3 -0
  37. package/plugin/src/claude_smart/env_config.py +4 -1
  38. package/plugin/src/claude_smart/events/post_tool.py +2 -1
  39. package/plugin/src/claude_smart/events/session_end.py +2 -1
  40. package/plugin/src/claude_smart/events/stop.py +3 -0
  41. package/plugin/src/claude_smart/events/user_prompt.py +2 -1
  42. package/plugin/src/claude_smart/internal_call.py +5 -2
  43. package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
  44. package/plugin/src/claude_smart/publish.py +59 -7
  45. package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
  46. package/plugin/src/claude_smart/runtime.py +15 -6
  47. package/plugin/src/claude_smart/state.py +211 -52
  48. package/plugin/uv.lock +1 -1
  49. package/plugin/vendor/reflexio/.env.example +13 -0
  50. package/plugin/vendor/reflexio/reflexio/README.md +7 -3
  51. package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
  52. package/plugin/vendor/reflexio/reflexio/client/client.py +126 -3
  53. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
  54. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
  55. package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
  56. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +177 -25
  57. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
  58. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
  59. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
  60. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
  61. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
  62. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
  63. package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
  64. package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
  65. package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
  66. package/plugin/vendor/reflexio/reflexio/server/api.py +66 -3
  67. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
  68. package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
  69. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
  70. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
  71. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +62 -5
  72. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
  73. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
  74. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
  75. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
  76. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
  77. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +20 -5
  78. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
  79. package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
  80. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
  81. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
  82. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
  83. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
  84. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
  85. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
  86. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
  87. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
  88. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
  89. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
  90. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
  91. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
  92. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
  93. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
  94. package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
  95. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
  96. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
  97. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
  98. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
  99. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
  100. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
  101. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
  102. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
  103. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
  104. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
  105. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
  106. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
  107. package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
  108. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +85 -16
  109. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
  110. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
  111. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
  112. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
  113. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
  114. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
  115. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
  116. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
  117. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +510 -63
  118. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
  120. package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
  121. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
  122. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
  123. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
  124. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
  125. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
  126. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
  127. package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
  128. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
  129. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
  130. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
  131. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
  132. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
  133. package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +122 -44
  134. package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
  135. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
  136. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
  137. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
  138. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
  139. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
  140. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
  141. package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
  142. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
  143. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
  144. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +1 -1
  145. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +50 -0
  146. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
  147. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
  148. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +36 -1
  149. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
  150. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
  151. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
  152. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
  153. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +43 -0
  154. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
  155. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +45 -7
  156. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +146 -6
  157. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +42 -7
  158. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +1 -1
  159. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
  160. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +50 -0
  161. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
  162. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
  163. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
  164. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
  165. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
  166. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
  167. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
  168. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
  169. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +59 -0
  170. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +52 -2
  171. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
  172. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
  173. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
  174. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
  175. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
  176. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
  177. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
@@ -3,18 +3,31 @@
3
3
 
4
4
  const { spawn, spawnSync } = require("node:child_process");
5
5
  const fs = require("node:fs");
6
- const http = require("node:http");
7
6
  const os = require("node:os");
8
7
  const path = require("node:path");
9
8
 
10
9
  const HOME = os.homedir();
11
10
  const STATE_DIR = path.join(HOME, ".claude-smart");
12
11
  const REFLEXIO_DIR = path.join(HOME, ".reflexio");
13
- const DEFAULT_BACKEND_PORT = 8071;
14
- const FALLBACK_BACKEND_PORT = 8072;
15
- const DASHBOARD_PORT = 3001;
12
+ const SERVICE_SCRIPT_TIMEOUT_MS = parsePositiveInteger(
13
+ process.env.CLAUDE_SMART_SERVICE_SCRIPT_TIMEOUT_MS,
14
+ 45_000,
15
+ );
16
+ const DEFAULT_BACKEND_PORT = parsePort(process.env.BACKEND_PORT, 8071);
17
+ const DEFAULT_EMBEDDING_PORT = parsePort(process.env.EMBEDDING_PORT, 8072);
18
+ const DASHBOARD_PORT = parsePort(process.env.DASHBOARD_PORT, parsePort(process.env.PORT, 3001));
16
19
  const LOG_MAX_BYTES = 10000000;
17
20
 
21
+ function parsePort(value, fallback) {
22
+ const port = Number(value);
23
+ return Number.isInteger(port) && port > 0 && port <= 65535 ? port : fallback;
24
+ }
25
+
26
+ function parsePositiveInteger(value, fallback) {
27
+ const parsed = Number(value);
28
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
29
+ }
30
+
18
31
  function emitOk() {
19
32
  process.stdout.write('{"continue":true}\n');
20
33
  }
@@ -207,27 +220,10 @@ function uvPath() {
207
220
  return commandPath(process.platform === "win32" ? ["uv.exe", "uv"] : ["uv"]);
208
221
  }
209
222
 
210
- function npmPath() {
211
- return commandPath(process.platform === "win32" ? ["npm.cmd", "npm.exe", "npm"] : ["npm"]);
212
- }
213
-
214
223
  function bashPath() {
215
224
  return commandPath(process.platform === "win32" ? ["bash.exe", "bash"] : ["bash"]);
216
225
  }
217
226
 
218
- function stateFile(name) {
219
- return path.join(STATE_DIR, name);
220
- }
221
-
222
- function backendUrlFile() {
223
- return stateFile("backend-url");
224
- }
225
-
226
- function writeBackendUrl(port) {
227
- ensureDir(STATE_DIR);
228
- fs.writeFileSync(backendUrlFile(), `http://localhost:${port}/\n`);
229
- }
230
-
231
227
  function unquoteEnvValue(value) {
232
228
  const trimmed = String(value || "").trim();
233
229
  if (trimmed.length >= 2) {
@@ -279,12 +275,6 @@ function loadReflexioEnv() {
279
275
  }
280
276
  }
281
277
 
282
- function reflexioUrlIsRemote() {
283
- const url = process.env.REFLEXIO_URL || "";
284
- if (!url) return false;
285
- return !/^http:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?::\d+)?\/?$/i.test(url);
286
- }
287
-
288
278
  function codexCompatPath(root) {
289
279
  const filename = process.platform === "win32"
290
280
  ? "codex-claude-compat.cmd"
@@ -294,69 +284,9 @@ function codexCompatPath(root) {
294
284
 
295
285
  function readBackendUrl() {
296
286
  if (process.env.REFLEXIO_URL) return process.env.REFLEXIO_URL;
297
- try {
298
- const value = fs.readFileSync(backendUrlFile(), "utf8").trim();
299
- if (value) return value;
300
- } catch {
301
- // Fall through to default.
302
- }
303
287
  return `http://localhost:${DEFAULT_BACKEND_PORT}/`;
304
288
  }
305
289
 
306
- function healthOk(port, pathname, markerHeader) {
307
- return new Promise((resolve) => {
308
- const req = http.request(
309
- {
310
- host: "127.0.0.1",
311
- port,
312
- path: pathname,
313
- method: "GET",
314
- timeout: 1200,
315
- },
316
- (res) => {
317
- const ok = res.statusCode && res.statusCode >= 200 && res.statusCode < 400;
318
- const markerOk = markerHeader ? Boolean(res.headers[markerHeader]) : true;
319
- res.resume();
320
- resolve(Boolean(ok && markerOk));
321
- },
322
- );
323
- req.on("timeout", () => req.destroy());
324
- req.on("error", () => resolve(false));
325
- req.end();
326
- });
327
- }
328
-
329
- function portOccupied(port) {
330
- return new Promise((resolve) => {
331
- const req = http.request(
332
- {
333
- host: "127.0.0.1",
334
- port,
335
- path: "/",
336
- method: "GET",
337
- timeout: 900,
338
- },
339
- (res) => {
340
- res.resume();
341
- resolve(true);
342
- },
343
- );
344
- req.on("timeout", () => req.destroy());
345
- req.on("error", (err) => {
346
- resolve(err && err.code !== "ECONNREFUSED");
347
- });
348
- req.end();
349
- });
350
- }
351
-
352
- async function waitForHealth(port, pathname, markerHeader, attempts) {
353
- for (let i = 0; i < attempts; i += 1) {
354
- if (await healthOk(port, pathname, markerHeader)) return true;
355
- await new Promise((resolve) => setTimeout(resolve, 1000));
356
- }
357
- return false;
358
- }
359
-
360
290
  function detached(command, args, options = {}) {
361
291
  const child = spawn(command, args, {
362
292
  cwd: options.cwd,
@@ -386,28 +316,52 @@ function startInstallerDetached(root, reason) {
386
316
  return true;
387
317
  }
388
318
 
389
- function readPid(file) {
390
- try {
391
- const value = fs.readFileSync(file, "utf8").trim();
392
- return value ? Number(value) : null;
393
- } catch {
394
- return null;
319
+ function runServiceScript(root, scriptName, action, logName) {
320
+ trimLog(path.join(STATE_DIR, logName));
321
+ const bash = bashPath();
322
+ const script = path.join(root, "scripts", scriptName);
323
+ if (!bash || !fs.existsSync(script)) {
324
+ appendLog(
325
+ logName,
326
+ `[claude-smart] codex hook: cannot run ${scriptName}; bash or script missing`,
327
+ );
328
+ emitOk();
329
+ return 0;
395
330
  }
396
- }
397
-
398
- function pidAlive(pid) {
399
- if (!pid || Number.isNaN(pid)) return false;
400
- try {
401
- process.kill(pid, 0);
402
- return true;
403
- } catch {
404
- return false;
331
+ const result = spawnSync(bash, [script, action], {
332
+ cwd: root,
333
+ env: {
334
+ ...process.env,
335
+ PLUGIN_ROOT: root,
336
+ CLAUDE_PLUGIN_ROOT: root,
337
+ CLAUDE_SMART_HOST: "codex",
338
+ CLAUDE_SMART_CLI_PATH: process.env.CLAUDE_SMART_CLI_PATH || codexCompatPath(root),
339
+ BACKEND_PORT: String(DEFAULT_BACKEND_PORT),
340
+ EMBEDDING_PORT: String(DEFAULT_EMBEDDING_PORT),
341
+ DASHBOARD_PORT: String(DASHBOARD_PORT),
342
+ PORT: String(DASHBOARD_PORT),
343
+ REFLEXIO_URL: readBackendUrl(),
344
+ },
345
+ stdio: ["ignore", "pipe", "pipe"],
346
+ timeout: SERVICE_SCRIPT_TIMEOUT_MS,
347
+ windowsHide: true,
348
+ });
349
+ const stderr = String(result.stderr || "").trim();
350
+ if (stderr) appendLog(logName, stderr);
351
+ if (result.error) {
352
+ appendLog(
353
+ logName,
354
+ `[claude-smart] codex hook: ${scriptName} ${action} failed: ${result.error.message}`,
355
+ );
405
356
  }
406
- }
407
-
408
- function writePid(file, pid) {
409
- ensureDir(path.dirname(file));
410
- fs.writeFileSync(file, `${pid}\n`);
357
+ if (result.signal) {
358
+ appendLog(
359
+ logName,
360
+ `[claude-smart] codex hook: ${scriptName} ${action} terminated by ${result.signal}`,
361
+ );
362
+ }
363
+ emitNormalizedHookOutput(result.stdout);
364
+ return typeof result.status === "number" ? result.status : 0;
411
365
  }
412
366
 
413
367
  function ensurePluginRoot(root) {
@@ -438,127 +392,11 @@ function ensurePluginRoot(root) {
438
392
  }
439
393
 
440
394
  async function startBackend(root) {
441
- trimLog(path.join(STATE_DIR, "backend.log"));
442
- if (process.env.CLAUDE_SMART_BACKEND_AUTOSTART === "0") {
443
- emitOk();
444
- return;
445
- }
446
- if (reflexioUrlIsRemote()) {
447
- appendLog("backend.log", "[claude-smart] backend: remote REFLEXIO_URL configured; skipping local backend start");
448
- emitOk();
449
- return;
450
- }
451
- const pidFile = path.join(STATE_DIR, "backend.pid");
452
- for (const port of [DEFAULT_BACKEND_PORT, FALLBACK_BACKEND_PORT]) {
453
- if (pidAlive(readPid(pidFile)) && await healthOk(port, "/health")) {
454
- writeBackendUrl(port);
455
- emitOk();
456
- return;
457
- }
458
- if (await healthOk(port, "/health")) {
459
- writeBackendUrl(port);
460
- emitOk();
461
- return;
462
- }
463
- }
464
- const uv = uvPath();
465
- if (!uv) {
466
- startInstallerDetached(root, "backend: uv not on PATH");
467
- }
468
- const readyUv = uvPath();
469
- if (!readyUv) {
470
- appendLog("backend.log", "[claude-smart] backend: uv not on PATH; installer recovery scheduled; skipping");
471
- emitOk();
472
- return;
473
- }
474
- let selectedPort = DEFAULT_BACKEND_PORT;
475
- if (await portOccupied(DEFAULT_BACKEND_PORT)) {
476
- appendLog("backend.log", "[claude-smart] backend: port 8071 occupied; trying 8072");
477
- selectedPort = FALLBACK_BACKEND_PORT;
478
- }
479
- const backendUrl = `http://localhost:${selectedPort}/`;
480
- const env = {
481
- ...process.env,
482
- BACKEND_PORT: String(selectedPort),
483
- REFLEXIO_URL: backendUrl,
484
- CLAUDE_SMART_USE_LOCAL_CLI: process.env.CLAUDE_SMART_USE_LOCAL_CLI || "1",
485
- CLAUDE_SMART_USE_LOCAL_EMBEDDING: process.env.CLAUDE_SMART_USE_LOCAL_EMBEDDING || "1",
486
- CLAUDE_SMART_HOST: "codex",
487
- CLAUDE_SMART_CLI_PATH: process.env.CLAUDE_SMART_CLI_PATH || codexCompatPath(root),
488
- INTERACTION_CLEANUP_THRESHOLD: process.env.INTERACTION_CLEANUP_THRESHOLD || "500",
489
- INTERACTION_CLEANUP_DELETE_COUNT: process.env.INTERACTION_CLEANUP_DELETE_COUNT || "200",
490
- };
491
- const pid = detached(
492
- readyUv,
493
- [
494
- "run",
495
- "--project",
496
- root,
497
- "--quiet",
498
- "reflexio",
499
- "services",
500
- "start",
501
- "--only",
502
- "backend",
503
- "--no-reload",
504
- ],
505
- { cwd: root, env },
506
- );
507
- writePid(pidFile, pid);
508
- if (await waitForHealth(selectedPort, "/health", null, 10)) {
509
- writeBackendUrl(selectedPort);
510
- }
511
- emitOk();
395
+ runServiceScript(root, "backend-service.sh", "start", "backend.log");
512
396
  }
513
397
 
514
398
  async function startDashboard(root) {
515
- if (process.env.CLAUDE_SMART_DASHBOARD_AUTOSTART === "0") {
516
- emitOk();
517
- return;
518
- }
519
- const dashboard = path.join(root, "dashboard");
520
- if (!fs.existsSync(dashboard)) {
521
- emitOk();
522
- return;
523
- }
524
- const pidFile = path.join(STATE_DIR, "dashboard.pid");
525
- if (
526
- pidAlive(readPid(pidFile)) &&
527
- await healthOk(DASHBOARD_PORT, "/api/health", "x-claude-smart-dashboard")
528
- ) {
529
- emitOk();
530
- return;
531
- }
532
- const npm = npmPath();
533
- if (!npm) {
534
- startInstallerDetached(root, "dashboard: npm not on PATH");
535
- }
536
- const readyNpm = npmPath();
537
- if (!readyNpm) {
538
- appendLog("dashboard.log", "[claude-smart] dashboard: npm not on PATH; installer recovery scheduled; skipping");
539
- emitOk();
540
- return;
541
- }
542
- if (!fs.existsSync(path.join(dashboard, ".next"))) {
543
- const buildPidFile = path.join(STATE_DIR, "dashboard-build.pid");
544
- if (!pidAlive(readPid(buildPidFile))) {
545
- const pid = detached(readyNpm, ["run", "build"], { cwd: dashboard });
546
- writePid(buildPidFile, pid);
547
- appendLog("dashboard.log", "[claude-smart] dashboard: .next missing; started background build");
548
- }
549
- emitOk();
550
- return;
551
- }
552
- const env = {
553
- ...process.env,
554
- PORT: String(DASHBOARD_PORT),
555
- REFLEXIO_URL: readBackendUrl(),
556
- CLAUDE_SMART_DASHBOARD_WORKSPACE: process.cwd(),
557
- };
558
- const pid = detached(readyNpm, ["run", "start"], { cwd: dashboard, env });
559
- writePid(pidFile, pid);
560
- await waitForHealth(DASHBOARD_PORT, "/api/health", "x-claude-smart-dashboard", 5);
561
- emitOk();
399
+ runServiceScript(root, "dashboard-service.sh", "start", "dashboard.log");
562
400
  }
563
401
 
564
402
  function runHook(root, event) {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Start backend + dashboard (idempotent), wait briefly for dashboard to come
3
- # up, print statuses, then open http://localhost:3001 in the default browser.
3
+ # up, print statuses, then open the dashboard in the default browser.
4
4
  #
5
5
  # Exists so the /claude-smart:dashboard slash command can invoke a single
6
6
  # plain `bash <script>` with no inline $(...) or ${...:-...} expansion in
@@ -18,6 +18,8 @@ STATE_DIR="$HOME/.claude-smart"
18
18
  BACKEND_LOG="$STATE_DIR/backend.log"
19
19
  DASHBOARD_LOG="$STATE_DIR/dashboard.log"
20
20
  DASHBOARD_UNAVAILABLE="$(claude_smart_dashboard_unavailable_marker)"
21
+ BACKEND_PORT="${BACKEND_PORT:-8071}"
22
+ DASHBOARD_PORT="${DASHBOARD_PORT:-${PORT:-3001}}"
21
23
 
22
24
  # Capture start-command output so we can surface fatal errors (e.g. uv/npm
23
25
  # missing, port collisions, .next not built) rather than silently swallow
@@ -64,7 +66,7 @@ failed=0
64
66
  if [ "$backend_status" = "not running" ]; then
65
67
  failed=1
66
68
  echo ""
67
- echo "ERROR: backend failed to start on http://localhost:8071"
69
+ echo "ERROR: backend failed to start on http://localhost:$BACKEND_PORT"
68
70
  [ -n "$backend_start_out" ] && echo "$backend_start_out"
69
71
  show_log_tail "backend" "$BACKEND_LOG"
70
72
  fi
@@ -75,7 +77,7 @@ if [ "$dashboard_status" = "not running" ]; then
75
77
  if claude_smart_pid_alive_file "$BUILD_PID_FILE"; then
76
78
  echo "dashboard: still building (first-run cost, ~1-2 min). Re-run /claude-smart:dashboard in a minute."
77
79
  else
78
- echo "ERROR: dashboard failed to start on http://localhost:3001"
80
+ echo "ERROR: dashboard failed to start on http://localhost:$DASHBOARD_PORT"
79
81
  [ -n "$dashboard_start_out" ] && echo "$dashboard_start_out"
80
82
  if [ -f "$DASHBOARD_UNAVAILABLE" ]; then
81
83
  echo ""
@@ -92,7 +94,7 @@ if [ "$failed" = "1" ]; then
92
94
  exit 1
93
95
  fi
94
96
 
95
- URL="http://localhost:3001"
97
+ URL="http://localhost:$DASHBOARD_PORT"
96
98
  PY_BIN=$(claude_smart_resolve_python || true)
97
99
  if [ -n "$PY_BIN" ] && "$PY_BIN" -m webbrowser "$URL"; then
98
100
  echo "Opened $URL"