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
@@ -10,6 +10,7 @@ import path from "node:path";
10
10
  import os from "node:os";
11
11
  import type {
12
12
  CitedItem,
13
+ Host,
13
14
  PlaybookApplicationStat,
14
15
  SessionDetail,
15
16
  SessionSummary,
@@ -18,6 +19,18 @@ import type {
18
19
  UserActionType,
19
20
  } from "./types";
20
21
 
22
+ const VALID_HOSTS: ReadonlySet<Host> = new Set([
23
+ "claude-code",
24
+ "codex",
25
+ "opencode",
26
+ "unknown",
27
+ ]);
28
+
29
+ function parseRecordedHost(value: unknown): Host | null {
30
+ if (typeof value !== "string") return null;
31
+ return VALID_HOSTS.has(value as Host) ? (value as Host) : null;
32
+ }
33
+
21
34
  // Mirrors _TOOL_DATA_FIELD_MAX_LEN in plugin/src/claude_smart/state.py — we
22
35
  // truncate to the same length the publisher ships to reflexio so the
23
36
  // dashboard renders the exact bytes the extractor sees.
@@ -41,6 +54,8 @@ type RawRecord = {
41
54
  content?: string;
42
55
  ts?: number;
43
56
  user_id?: string;
57
+ host?: string;
58
+ request_id?: string;
44
59
  tool_name?: string;
45
60
  tool_input?: Record<string, unknown>;
46
61
  tool_output?: string;
@@ -280,6 +295,8 @@ function foldTurns(records: RawRecord[]): {
280
295
  lastTs: number | null;
281
296
  firstTs: number | null;
282
297
  preview: string | null;
298
+ host: Host | null;
299
+ requestIds: string[];
283
300
  } {
284
301
  let published = 0;
285
302
  let pendingTools: ToolUsed[] = [];
@@ -288,9 +305,17 @@ function foldTurns(records: RawRecord[]): {
288
305
  let lastTs: number | null = null;
289
306
  let firstTs: number | null = null;
290
307
  let preview: string | null = null;
308
+ let host: Host | null = null;
309
+ const requestIds = new Set<string>();
291
310
 
292
311
  for (let idx = 0; idx < records.length; idx++) {
293
312
  const rec = records[idx];
313
+ if (host === null && rec.host !== undefined) {
314
+ host = parseRecordedHost(rec.host);
315
+ }
316
+ if (typeof rec.request_id === "string" && rec.request_id) {
317
+ requestIds.add(rec.request_id);
318
+ }
294
319
  if (typeof rec.published_up_to === "number") {
295
320
  published = rec.published_up_to;
296
321
  pendingTools = [];
@@ -366,6 +391,8 @@ function foldTurns(records: RawRecord[]): {
366
391
  lastTs,
367
392
  firstTs,
368
393
  preview,
394
+ host,
395
+ requestIds: Array.from(requestIds),
369
396
  };
370
397
  }
371
398
 
@@ -391,6 +418,8 @@ export async function listSessions(): Promise<SessionSummary[]> {
391
418
  lastTs,
392
419
  firstTs,
393
420
  preview,
421
+ host,
422
+ requestIds,
394
423
  } = foldTurns(records);
395
424
  summaries.push({
396
425
  session_id: entry.replace(/\.jsonl$/, ""),
@@ -401,6 +430,8 @@ export async function listSessions(): Promise<SessionSummary[]> {
401
430
  published_up_to: publishedUpTo,
402
431
  preview,
403
432
  source: "local",
433
+ host,
434
+ request_ids: requestIds,
404
435
  });
405
436
  }
406
437
  summaries.sort((a, b) => (b.last_activity ?? 0) - (a.last_activity ?? 0));
@@ -456,6 +487,13 @@ export async function readSession(
456
487
  } catch {
457
488
  return null;
458
489
  }
459
- const { turns, publishedUpTo } = foldTurns(records);
460
- return { session_id: sessionId, turns, published_up_to: publishedUpTo };
490
+ const { turns, publishedUpTo, learningInteractionCount, host } =
491
+ foldTurns(records);
492
+ return {
493
+ session_id: sessionId,
494
+ turns,
495
+ learning_interaction_count: learningInteractionCount,
496
+ published_up_to: publishedUpTo,
497
+ host,
498
+ };
461
499
  }
@@ -41,6 +41,8 @@ export interface Interaction {
41
41
  tools_used: ToolUsed[];
42
42
  }
43
43
 
44
+ export type Host = "claude-code" | "codex" | "opencode" | "unknown";
45
+
44
46
  export interface UserPlaybook {
45
47
  user_playbook_id: number;
46
48
  user_id: string | null;
@@ -103,12 +105,16 @@ export interface SessionSummary {
103
105
  published_up_to: number;
104
106
  preview: string | null;
105
107
  source: "local";
108
+ host: Host | null;
109
+ request_ids: string[];
106
110
  }
107
111
 
108
112
  export interface SessionDetail {
109
113
  session_id: string;
110
114
  turns: SessionTurn[];
115
+ learning_interaction_count: number;
111
116
  published_up_to: number;
117
+ host: Host | null;
112
118
  }
113
119
 
114
120
  export interface ClaudeSmartConfig {
@@ -167,7 +173,7 @@ export interface ReflexioConfig {
167
173
  export interface PlaybookApplicationStat {
168
174
  real_id: string;
169
175
  citation_id?: string;
170
- kind: "playbook" | "profile";
176
+ kind: "playbook" | "profile" | "user_playbook" | "agent_playbook";
171
177
  source_kind?: "user_playbook" | "agent_playbook" | "profile";
172
178
  title: string;
173
179
  href?: string;
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claude-smart"
3
- version = "0.2.49"
3
+ version = "0.2.50"
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",
@@ -143,12 +143,23 @@ claude_smart_reflexio_url_is_remote() {
143
143
  return 0
144
144
  }
145
145
 
146
+ claude_smart_derive_reflexio_url_from_backend_port() {
147
+ local port default_url
148
+ port="${BACKEND_PORT:-8071}"
149
+ default_url="http://localhost:$port/"
150
+ case "${REFLEXIO_URL:-}" in
151
+ ""|"http://localhost:8071"|"http://localhost:8071/"|"http://127.0.0.1:8071"|"http://127.0.0.1:8071/")
152
+ export REFLEXIO_URL="$default_url"
153
+ ;;
154
+ esac
155
+ }
156
+
146
157
  claude_smart_is_internal_invocation_env() {
147
158
  if [ "${CLAUDE_SMART_INTERNAL:-}" = "1" ]; then
148
159
  return 0
149
160
  fi
150
161
  case "${CLAUDE_CODE_ENTRYPOINT:-}" in
151
- ""|"cli"|"claude-desktop") return 1 ;;
162
+ ""|"cli"|"claude-desktop"|"claude-vscode"|"claude-jetbrains") return 1 ;;
152
163
  *) return 0 ;;
153
164
  esac
154
165
  }
@@ -703,6 +714,191 @@ claude_smart_kill_tree() {
703
714
  kill -KILL -- "-$pid" 2>/dev/null || true
704
715
  }
705
716
 
717
+ claude_smart_now_epoch() {
718
+ date +%s 2>/dev/null || {
719
+ _CS_PY=$(claude_smart_resolve_python 2>/dev/null || true)
720
+ [ -n "$_CS_PY" ] || return 1
721
+ "$_CS_PY" - <<'PY'
722
+ import time
723
+
724
+ print(int(time.time()))
725
+ PY
726
+ }
727
+ }
728
+
729
+ claude_smart_path_mtime_epoch() {
730
+ path="$1"
731
+ mtime=""
732
+ [ -e "$path" ] || return 1
733
+ mtime="$(stat -f %m "$path" 2>/dev/null || true)"
734
+ case "$mtime" in
735
+ ''|*[!0-9]*) ;;
736
+ *) printf '%s\n' "$mtime"; return 0 ;;
737
+ esac
738
+ mtime="$(stat -c %Y "$path" 2>/dev/null || true)"
739
+ case "$mtime" in
740
+ ''|*[!0-9]*) ;;
741
+ *) printf '%s\n' "$mtime"; return 0 ;;
742
+ esac
743
+ _CS_PY=$(claude_smart_resolve_python 2>/dev/null || true)
744
+ [ -n "$_CS_PY" ] || return 1
745
+ "$_CS_PY" - "$path" <<'PY'
746
+ import os
747
+ import sys
748
+
749
+ print(int(os.path.getmtime(sys.argv[1])))
750
+ PY
751
+ }
752
+
753
+ claude_smart_service_lock_stale_seconds() {
754
+ value="${CLAUDE_SMART_SERVICE_LOCK_STALE_SECONDS:-60}"
755
+ case "$value" in
756
+ ''|*[!0-9]*) printf '%s\n' "60" ;;
757
+ *) printf '%s\n' "$value" ;;
758
+ esac
759
+ }
760
+
761
+ claude_smart_service_lock() {
762
+ name="$1"
763
+ lock_dir="$HOME/.claude-smart/$name.start.lock"
764
+ pid_file="$lock_dir/pid"
765
+ mkdir -p "$HOME/.claude-smart"
766
+ for _ in 1 2 3; do
767
+ if mkdir "$lock_dir" 2>/dev/null; then
768
+ printf '%s\n' "$$" > "$pid_file"
769
+ return 0
770
+ fi
771
+
772
+ lock_pid="$(cat "$pid_file" 2>/dev/null || true)"
773
+ now="$(claude_smart_now_epoch 2>/dev/null || printf '%s\n' "0")"
774
+ mtime="$(claude_smart_path_mtime_epoch "$lock_dir" 2>/dev/null || printf '%s\n' "$now")"
775
+ age=$((now - mtime))
776
+ stale_after="$(claude_smart_service_lock_stale_seconds)"
777
+
778
+ if [ -n "$lock_pid" ] && kill -0 "$lock_pid" 2>/dev/null; then
779
+ return 1
780
+ fi
781
+ if [ -z "$lock_pid" ] && [ "$age" -lt "$stale_after" ]; then
782
+ return 1
783
+ fi
784
+ if [ -n "$lock_pid" ] || [ "$age" -ge "$stale_after" ]; then
785
+ stale_dir="$lock_dir.stale.$$"
786
+ if mv "$lock_dir" "$stale_dir" 2>/dev/null; then
787
+ rm -rf "$stale_dir"
788
+ continue
789
+ fi
790
+ fi
791
+ return 1
792
+ done
793
+ return 1
794
+ }
795
+
796
+ claude_smart_service_unlock() {
797
+ name="$1"
798
+ lock_dir="$HOME/.claude-smart/$name.start.lock"
799
+ pid_file="$lock_dir/pid"
800
+ [ -d "$lock_dir" ] || return 0
801
+ lock_pid="$(cat "$pid_file" 2>/dev/null || true)"
802
+ [ "$lock_pid" = "$$" ] || return 0
803
+ rm -rf "$lock_dir"
804
+ }
805
+
806
+ claude_smart_powershell_bin() {
807
+ for cand in powershell.exe powershell pwsh.exe pwsh; do
808
+ if command -v "$cand" >/dev/null 2>&1; then
809
+ command -v "$cand"
810
+ return 0
811
+ fi
812
+ done
813
+ return 1
814
+ }
815
+
816
+ claude_smart_port_listener_pids() {
817
+ port="$1"
818
+ if claude_smart_is_windows; then
819
+ ps_bin="$(claude_smart_powershell_bin 2>/dev/null || true)"
820
+ if [ -n "$ps_bin" ]; then
821
+ PORT="$port" "$ps_bin" -NoProfile -ExecutionPolicy Bypass -Command \
822
+ '$ErrorActionPreference="SilentlyContinue"; Get-NetTCPConnection -LocalPort $env:PORT -State Listen | Select-Object -ExpandProperty OwningProcess' \
823
+ 2>/dev/null | tr -d '\r' | awk 'NF && !seen[$1]++ {print $1}'
824
+ return 0
825
+ fi
826
+ if command -v netstat >/dev/null 2>&1; then
827
+ netstat -ano 2>/dev/null | awk -v port=":$port" '
828
+ $1 ~ /^TCP/ && $2 ~ port "$" && $4 == "LISTENING" && !seen[$5]++ {print $5}
829
+ '
830
+ return 0
831
+ fi
832
+ fi
833
+ command -v lsof >/dev/null 2>&1 || return 0
834
+ lsof -tiTCP:"$port" -sTCP:LISTEN -P -n 2>/dev/null | awk 'NF && !seen[$1]++ {print $1}'
835
+ }
836
+
837
+ claude_smart_pid_command() {
838
+ pid="$1"
839
+ if claude_smart_is_windows; then
840
+ ps_bin="$(claude_smart_powershell_bin 2>/dev/null || true)"
841
+ if [ -n "$ps_bin" ]; then
842
+ PID="$pid" "$ps_bin" -NoProfile -ExecutionPolicy Bypass -Command \
843
+ '$ErrorActionPreference="SilentlyContinue"; $p=Get-CimInstance Win32_Process -Filter "ProcessId=$env:PID"; if ($p) { $p.CommandLine }' \
844
+ 2>/dev/null | tr -d '\r'
845
+ return 0
846
+ fi
847
+ if command -v tasklist >/dev/null 2>&1; then
848
+ tasklist //FI "PID eq $pid" //FO CSV //NH 2>/dev/null \
849
+ | awk -F, 'NR==1 {gsub(/^"|"$/, "", $1); print $1}'
850
+ return 0
851
+ fi
852
+ fi
853
+ command -v ps >/dev/null 2>&1 || return 0
854
+ ps -ww -p "$pid" -o command= 2>/dev/null || true
855
+ }
856
+
857
+ claude_smart_pid_parent() {
858
+ pid="$1"
859
+ if claude_smart_is_windows; then
860
+ ps_bin="$(claude_smart_powershell_bin 2>/dev/null || true)"
861
+ if [ -n "$ps_bin" ]; then
862
+ PID="$pid" "$ps_bin" -NoProfile -ExecutionPolicy Bypass -Command \
863
+ '$ErrorActionPreference="SilentlyContinue"; $p=Get-CimInstance Win32_Process -Filter "ProcessId=$env:PID"; if ($p) { $p.ParentProcessId }' \
864
+ 2>/dev/null | tr -d '\r'
865
+ return 0
866
+ fi
867
+ fi
868
+ command -v ps >/dev/null 2>&1 || return 0
869
+ ps -p "$pid" -o ppid= 2>/dev/null | awk 'NF {print $1; exit}' || true
870
+ }
871
+
872
+ claude_smart_port_holder() {
873
+ port="$1"
874
+ pid="$(claude_smart_port_listener_pids "$port" | head -n1)"
875
+ [ -n "$pid" ] || return 0
876
+ cmdline="$(claude_smart_pid_command "$pid" | head -n1)"
877
+ [ -n "$cmdline" ] || cmdline="unknown"
878
+ printf '%s (pid %s)\n' "$cmdline" "$pid"
879
+ }
880
+
881
+ claude_smart_reap_port_listeners_matching() {
882
+ port="$1"
883
+ shift || true
884
+ [ "$#" -gt 0 ] || return 1
885
+ killed=1
886
+ pids="$(claude_smart_port_listener_pids "$port" || true)"
887
+ [ -n "$pids" ] || return 1
888
+ for pid in $pids; do
889
+ cmdline="$(claude_smart_pid_command "$pid" | tr '\n' ' ' || true)"
890
+ [ -n "$cmdline" ] || continue
891
+ for pattern in "$@"; do
892
+ if [[ "$cmdline" == $pattern ]]; then
893
+ claude_smart_kill_tree "$pid"
894
+ killed=0
895
+ break
896
+ fi
897
+ done
898
+ done
899
+ return "$killed"
900
+ }
901
+
706
902
  # Return 0 (true) if $1 names a pid file whose pid is currently alive.
707
903
  # Silent on missing/empty/stale files.
708
904
  claude_smart_pid_alive_file() {
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ """Run Reflexio CLI with claude-smart metadata visible in process argv."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import runpy
8
+ import sys
9
+
10
+
11
+ def main() -> int:
12
+ """Mirror claude-smart metadata into env and delegate to Reflexio CLI.
13
+
14
+ Args:
15
+ None: Reads process argv. Arguments before ``--`` are
16
+ ``--claude-smart-*`` metadata kept visible in the process command
17
+ line; arguments after ``--`` become ``reflexio.cli`` arguments.
18
+
19
+ Returns:
20
+ int: Exit status. Returns ``2`` when required argument separators or
21
+ Reflexio CLI arguments are missing.
22
+ """
23
+ try:
24
+ separator = sys.argv.index("--")
25
+ except ValueError:
26
+ return 2
27
+
28
+ metadata_args = sys.argv[1:separator]
29
+ reflexio_args = sys.argv[separator + 1 :]
30
+ if not reflexio_args:
31
+ return 2
32
+
33
+ for arg in metadata_args:
34
+ if not arg.startswith("--claude-smart-") or "=" not in arg:
35
+ continue
36
+ key, value = arg[2:].split("=", 1)
37
+ env_key = key.replace("-", "_").upper()
38
+ os.environ.setdefault(env_key, value)
39
+
40
+ sys.argv = ["reflexio.cli", *reflexio_args]
41
+ runpy.run_module("reflexio.cli", run_name="__main__")
42
+ return 0
43
+
44
+
45
+ if __name__ == "__main__":
46
+ raise SystemExit(main())