claude-smart 0.2.42 → 0.2.44

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 (402) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +1 -1
  3. package/bin/claude-smart.js +2 -2
  4. package/package.json +9 -3
  5. package/plugin/.claude-plugin/plugin.json +9 -3
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +23 -3
  8. package/plugin/pyproject.toml +3 -3
  9. package/plugin/scripts/_lib.sh +91 -0
  10. package/plugin/scripts/backend-service.sh +51 -4
  11. package/plugin/scripts/cli.sh +3 -1
  12. package/plugin/scripts/codex-hook.js +72 -4
  13. package/plugin/scripts/dashboard-build.sh +1 -0
  14. package/plugin/scripts/dashboard-service.sh +1 -0
  15. package/plugin/scripts/ensure-plugin-root.sh +1 -0
  16. package/plugin/scripts/hook_entry.sh +6 -3
  17. package/plugin/scripts/smart-install.sh +3 -2
  18. package/plugin/src/README.md +57 -0
  19. package/plugin/src/claude_smart/context_format.py +11 -12
  20. package/plugin/src/claude_smart/cs_cite.py +26 -12
  21. package/plugin/src/claude_smart/ids.py +13 -5
  22. package/plugin/uv.lock +126 -5
  23. package/plugin/vendor/reflexio/.env.example +62 -0
  24. package/plugin/vendor/reflexio/LICENSE +201 -0
  25. package/plugin/vendor/reflexio/README.md +338 -0
  26. package/plugin/vendor/reflexio/pyproject.toml +274 -0
  27. package/plugin/vendor/reflexio/reflexio/README.md +184 -0
  28. package/plugin/vendor/reflexio/reflexio/__init__.py +166 -0
  29. package/plugin/vendor/reflexio/reflexio/benchmarks/__init__.py +1 -0
  30. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/README.md +109 -0
  31. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/__init__.py +1 -0
  32. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/backends.py +175 -0
  33. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/bench.py +642 -0
  34. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/embed_cache.py +330 -0
  35. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/report.py +317 -0
  36. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/report.md +43 -0
  37. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/results.json +4478 -0
  38. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/scenarios.py +134 -0
  39. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/seed.py +255 -0
  40. package/plugin/vendor/reflexio/reflexio/cli/README.md +287 -0
  41. package/plugin/vendor/reflexio/reflexio/cli/__init__.py +0 -0
  42. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +56 -0
  43. package/plugin/vendor/reflexio/reflexio/cli/_client.py +86 -0
  44. package/plugin/vendor/reflexio/reflexio/cli/app.py +127 -0
  45. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +265 -0
  46. package/plugin/vendor/reflexio/reflexio/cli/codex_auth.py +503 -0
  47. package/plugin/vendor/reflexio/reflexio/cli/commands/__init__.py +0 -0
  48. package/plugin/vendor/reflexio/reflexio/cli/commands/admin_cmd.py +65 -0
  49. package/plugin/vendor/reflexio/reflexio/cli/commands/agent_playbooks.py +503 -0
  50. package/plugin/vendor/reflexio/reflexio/cli/commands/api.py +114 -0
  51. package/plugin/vendor/reflexio/reflexio/cli/commands/auth.py +109 -0
  52. package/plugin/vendor/reflexio/reflexio/cli/commands/config_cmd.py +511 -0
  53. package/plugin/vendor/reflexio/reflexio/cli/commands/doctor.py +127 -0
  54. package/plugin/vendor/reflexio/reflexio/cli/commands/embeddings.py +53 -0
  55. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +478 -0
  56. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +303 -0
  57. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +289 -0
  58. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +964 -0
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +285 -0
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/status_cmd.py +143 -0
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/user_playbooks.py +373 -0
  62. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +284 -0
  63. package/plugin/vendor/reflexio/reflexio/cli/errors.py +217 -0
  64. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +247 -0
  65. package/plugin/vendor/reflexio/reflexio/cli/output.py +867 -0
  66. package/plugin/vendor/reflexio/reflexio/cli/paths.py +41 -0
  67. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +391 -0
  68. package/plugin/vendor/reflexio/reflexio/cli/state.py +204 -0
  69. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +96 -0
  70. package/plugin/vendor/reflexio/reflexio/cli/utils.py +329 -0
  71. package/plugin/vendor/reflexio/reflexio/client/__init__.py +3 -0
  72. package/plugin/vendor/reflexio/reflexio/client/cache.py +150 -0
  73. package/plugin/vendor/reflexio/reflexio/client/client.py +2613 -0
  74. package/plugin/vendor/reflexio/reflexio/defaults.py +23 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/__init__.py +0 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/.clawhubignore +7 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +274 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +517 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +473 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +2156 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +18 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +241 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +140 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +130 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +113 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +52 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +103 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +156 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +134 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +41 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +17 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +24 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +48 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +278 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +164 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +36 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +35 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +27 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +31 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +42 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +49 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +91 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +50 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tsconfig.json +16 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/types/openclaw.d.ts +230 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +13 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/README.md +120 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/TESTING.md +168 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package-lock.json +1657 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package.json +16 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/HEARTBEAT.md +6 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/README.md +84 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/SKILL.md +194 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/_meta.json +6 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/agents/reflexio-extractor.md +45 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/handler.ts +214 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/setup.ts +55 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/index.ts +327 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/consolidate.ts +233 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/dedup.ts +80 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/io.ts +155 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/openclaw-cli.ts +67 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/search.ts +33 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-playbook.ts +76 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-profile.ts +79 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/openclaw.plugin.json +46 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/package.json +18 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/README.md +36 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/full_consolidation.md +56 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/playbook_extraction.md +217 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/profile_extraction.md +132 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-consolidate/SKILL.md +33 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-embedded/SKILL.md +194 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/HOOK.md +18 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/architecture.md +49 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/comparison.md +31 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/future-work.md +47 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/porting-notes.md +52 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/install.sh +52 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/uninstall.sh +36 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/consolidate.test.ts +135 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/dedup.test.ts +104 -0
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/io.test.ts +175 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/search.test.ts +66 -0
  145. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/smoke-test.ts +140 -0
  146. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-playbook.test.ts +93 -0
  147. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-profile.test.ts +174 -0
  148. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tsconfig.json +16 -0
  149. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/types/openclaw.d.ts +230 -0
  150. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/vitest.config.ts +7 -0
  151. package/plugin/vendor/reflexio/reflexio/lib/__init__.py +23 -0
  152. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +310 -0
  153. package/plugin/vendor/reflexio/reflexio/lib/_base.py +225 -0
  154. package/plugin/vendor/reflexio/reflexio/lib/_config.py +83 -0
  155. package/plugin/vendor/reflexio/reflexio/lib/_dashboard.py +266 -0
  156. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +176 -0
  157. package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +334 -0
  158. package/plugin/vendor/reflexio/reflexio/lib/_operations.py +153 -0
  159. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +545 -0
  160. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +52 -0
  161. package/plugin/vendor/reflexio/reflexio/lib/_search.py +167 -0
  162. package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +103 -0
  163. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +288 -0
  164. package/plugin/vendor/reflexio/reflexio/lib/reflexio_lib.py +27 -0
  165. package/plugin/vendor/reflexio/reflexio/models/__init__.py +0 -0
  166. package/plugin/vendor/reflexio/reflexio/models/api_schema/__init__.py +0 -0
  167. package/plugin/vendor/reflexio/reflexio/models/api_schema/braintrust_schema.py +141 -0
  168. package/plugin/vendor/reflexio/reflexio/models/api_schema/common.py +41 -0
  169. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +3 -0
  170. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +1112 -0
  171. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +63 -0
  172. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +487 -0
  173. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +28 -0
  174. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +83 -0
  175. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +768 -0
  176. package/plugin/vendor/reflexio/reflexio/models/api_schema/service_schemas.py +9 -0
  177. package/plugin/vendor/reflexio/reflexio/models/api_schema/stall_state_schema.py +32 -0
  178. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/__init__.py +3 -0
  179. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +177 -0
  180. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +129 -0
  181. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/enums.py +25 -0
  182. package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +333 -0
  183. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +908 -0
  184. package/plugin/vendor/reflexio/reflexio/models/py.typed +0 -0
  185. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +90 -0
  186. package/plugin/vendor/reflexio/reflexio/server/README.md +622 -0
  187. package/plugin/vendor/reflexio/reflexio/server/__init__.py +210 -0
  188. package/plugin/vendor/reflexio/reflexio/server/__main__.py +132 -0
  189. package/plugin/vendor/reflexio/reflexio/server/_auth.py +25 -0
  190. package/plugin/vendor/reflexio/reflexio/server/api.py +2868 -0
  191. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +34 -0
  192. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/account_api.py +143 -0
  193. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/health_api.py +91 -0
  194. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/pending_tool_call_api.py +572 -0
  195. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +66 -0
  196. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +562 -0
  197. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +50 -0
  198. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/stall_state_api.py +100 -0
  199. package/plugin/vendor/reflexio/reflexio/server/cache/__init__.py +15 -0
  200. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +208 -0
  201. package/plugin/vendor/reflexio/reflexio/server/correlation.py +46 -0
  202. package/plugin/vendor/reflexio/reflexio/server/llm/__init__.py +30 -0
  203. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +359 -0
  204. package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
  205. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1871 -0
  206. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +140 -0
  207. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +479 -0
  208. package/plugin/vendor/reflexio/reflexio/server/llm/providers/__init__.py +1 -0
  209. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1122 -0
  210. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_stream_parser.py +197 -0
  211. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +338 -0
  212. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +213 -0
  213. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +288 -0
  214. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/__init__.py +6 -0
  215. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +187 -0
  216. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
  217. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +716 -0
  218. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +179 -0
  219. package/plugin/vendor/reflexio/reflexio/server/prompt/__init__.py +0 -0
  220. package/plugin/vendor/reflexio/reflexio/server/prompt/_dispatchers.py +54 -0
  221. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/README.md +121 -0
  222. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation/v1.0.0.prompt.md +58 -0
  223. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation_with_comparison/v1.0.0.prompt.md +76 -0
  224. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/answer_synthesis/v1.5.2.prompt.md +88 -0
  225. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/compress_session_for_query/v1.3.0.prompt.md +31 -0
  226. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +20 -0
  227. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.0.0.prompt.md +53 -0
  228. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.1.0.prompt.md +57 -0
  229. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.2.0.prompt.md +68 -0
  230. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.3.0.prompt.md +70 -0
  231. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.4.0.prompt.md +77 -0
  232. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.5.0.prompt.md +82 -0
  233. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +83 -0
  234. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.1.0.prompt.md +193 -0
  235. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +206 -0
  236. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0-deprecated.prompt.md +66 -0
  237. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0.prompt.md +43 -0
  238. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.1.0.prompt.md +46 -0
  239. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0-deprecated.prompt.md +64 -0
  240. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0.prompt.md +39 -0
  241. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.1.0.prompt.md +39 -0
  242. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.2.0.prompt.md +47 -0
  243. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +58 -0
  244. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.1.prompt.md +69 -0
  245. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +71 -0
  246. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.0.2.prompt.md +254 -0
  247. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.1.0.prompt.md +274 -0
  248. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +283 -0
  249. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.2.prompt.md +234 -0
  250. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +244 -0
  251. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v1.0.0.prompt.md +73 -0
  252. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v2.0.0.prompt.md +86 -0
  253. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.0.0.prompt.md +97 -0
  254. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.1.0.prompt.md +119 -0
  255. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.2.0.prompt.md +123 -0
  256. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +137 -0
  257. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.0.0.prompt.md +14 -0
  258. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.1.0.prompt.md +24 -0
  259. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +29 -0
  260. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.0.0.prompt.md +11 -0
  261. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.1.0.prompt.md +21 -0
  262. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.2.0.prompt.md +25 -0
  263. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.0.0.prompt.md +37 -0
  264. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.1.0.prompt.md +40 -0
  265. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.2.0.prompt.md +36 -0
  266. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v1.0.0.prompt.md +45 -0
  267. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v2.0.0.prompt.md +81 -0
  268. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v3.0.0.prompt.md +80 -0
  269. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate_expert/v1.0.0.prompt.md +34 -0
  270. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_deduplication/v1.0.0.prompt.md +116 -0
  271. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate/v1.0.0.prompt.md +33 -0
  272. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate_override/v1.0.0.prompt.md +16 -0
  273. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +140 -0
  274. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +160 -0
  275. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_main/v1.0.0.prompt.md +14 -0
  276. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +19 -0
  277. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/rerank_relevance/v1.1.0.prompt.md +44 -0
  278. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +43 -0
  279. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_content_evaluation/v1.0.0.prompt.md +33 -0
  280. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/feedback_extraction_main_v1.jsonl +10 -0
  281. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/profile_update_main_v1.jsonl +10 -0
  282. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +280 -0
  283. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_schema.py +11 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/README.md +58 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +131 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +60 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py +228 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +87 -0
  289. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluator.py +372 -0
  290. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/delayed_group_evaluator.py +156 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +336 -0
  292. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +471 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +1668 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/__init__.py +0 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_cron.py +196 -0
  296. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_encryption.py +101 -0
  297. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/client.py +167 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/service.py +281 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +179 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/configurator/config_storage.py +62 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/configurator/configurator.py +87 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/configurator/local_file_config_storage.py +187 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/configurator/test_config_storage.py +162 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +112 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/embedding_text.py +62 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/__init__.py +0 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/distribution.py +33 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +126 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +192 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/hero_state.py +75 -0
  311. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/rule_attribution.py +97 -0
  312. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +515 -0
  313. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/shadow_aggregation.py +90 -0
  314. package/plugin/vendor/reflexio/reflexio/server/services/extraction/__init__.py +0 -0
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +91 -0
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +303 -0
  317. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +25 -0
  318. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +358 -0
  319. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +138 -0
  320. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +217 -0
  321. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +535 -0
  322. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +171 -0
  323. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +779 -0
  324. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
  325. package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +94 -0
  326. package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
  327. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +702 -0
  328. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +835 -0
  329. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +89 -0
  330. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_aggregator.py +1388 -0
  331. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +1045 -0
  332. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_extractor.py +436 -0
  333. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_generation_service.py +808 -0
  334. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_constants.py +28 -0
  335. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +362 -0
  336. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/__init__.py +24 -0
  337. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/assistant_webhook.py +246 -0
  338. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/gepa_adapter.py +291 -0
  339. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/judge.py +97 -0
  340. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +96 -0
  341. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +645 -0
  342. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/rollout.py +35 -0
  343. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scenario_resolver.py +93 -0
  344. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +174 -0
  345. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/__init__.py +26 -0
  346. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_document_expander.py +179 -0
  347. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +297 -0
  348. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +772 -0
  349. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_extractor.py +462 -0
  350. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +737 -0
  351. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +290 -0
  352. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +17 -0
  353. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_extractor.py +247 -0
  354. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +803 -0
  355. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +146 -0
  356. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/__init__.py +0 -0
  357. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +80 -0
  358. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
  359. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +756 -0
  360. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/__init__.py +1 -0
  361. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +184 -0
  362. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +81 -0
  363. package/plugin/vendor/reflexio/reflexio/server/services/storage/constants.py +2 -0
  364. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +11 -0
  365. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +154 -0
  366. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +155 -0
  367. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +59 -0
  368. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +1298 -0
  369. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +1945 -0
  370. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +600 -0
  371. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +346 -0
  372. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1378 -0
  373. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +747 -0
  374. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +263 -0
  375. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +193 -0
  376. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +166 -0
  377. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +217 -0
  378. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +153 -0
  379. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +384 -0
  380. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +71 -0
  381. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +235 -0
  382. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +170 -0
  383. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +677 -0
  384. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +250 -0
  385. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +154 -0
  386. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +130 -0
  387. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +93 -0
  388. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_stall_state.py +76 -0
  389. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +572 -0
  390. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +77 -0
  391. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +116 -0
  392. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_manager.py +263 -0
  393. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +13 -0
  394. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/llm_model_setting.json +7 -0
  395. package/plugin/vendor/reflexio/reflexio/server/tracing.py +158 -0
  396. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +113 -0
  397. package/plugin/vendor/reflexio/reflexio/server/uvicorn_logging.py +76 -0
  398. package/plugin/vendor/reflexio/reflexio/test_support/__init__.py +1 -0
  399. package/plugin/vendor/reflexio/reflexio/test_support/llm_fixtures.py +62 -0
  400. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +242 -0
  401. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +129 -0
  402. package/plugin/vendor/reflexio/reflexio/test_support/skip_decorators.py +43 -0
@@ -0,0 +1,867 @@
1
+ """Structured output formatting for Reflexio CLI.
2
+
3
+ All commands produce output through this module, ensuring a consistent
4
+ JSON envelope for agent consumption and Rich formatting for humans.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import sys
11
+ from datetime import UTC
12
+ from typing import Any
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # JSON envelope
16
+ # ---------------------------------------------------------------------------
17
+
18
+
19
+ def render(
20
+ data: Any,
21
+ *,
22
+ json_mode: bool = False,
23
+ meta: dict[str, Any] | None = None,
24
+ exclude_none: bool = True,
25
+ ) -> None:
26
+ """Render command output to stdout.
27
+
28
+ In JSON mode, wraps data in a standard envelope:
29
+ ``{"ok": true, "data": ..., "meta": {...}}``
30
+
31
+ In human mode, prints the data as-is (string) or pretty-prints dicts/lists.
32
+
33
+ Args:
34
+ data: The data to render (dict, list, string, or Pydantic model)
35
+ json_mode: If True, output JSON envelope; otherwise human-readable
36
+ meta: Optional metadata (count, limit, has_more, etc.)
37
+ exclude_none: If True (default), omit None fields from Pydantic models
38
+ """
39
+ if json_mode:
40
+ envelope: dict[str, Any] = {
41
+ "ok": True,
42
+ "data": _serialize(data, exclude_none=exclude_none),
43
+ }
44
+ if meta:
45
+ envelope["meta"] = meta
46
+ print(json.dumps(envelope, indent=2, default=str))
47
+ elif isinstance(data, str):
48
+ print(data)
49
+ elif isinstance(data, (dict, list)):
50
+ print(json.dumps(data, indent=2, default=str))
51
+ elif hasattr(data, "model_dump"):
52
+ # Pydantic model safety net — serialize to dict instead of __repr__
53
+ print(
54
+ json.dumps(
55
+ data.model_dump(mode="json", exclude_none=exclude_none),
56
+ indent=2,
57
+ default=str,
58
+ )
59
+ )
60
+ else:
61
+ print(data)
62
+
63
+
64
+ def _serialize(data: Any, *, exclude_none: bool = True) -> Any:
65
+ """Serialize data for JSON output, handling Pydantic models.
66
+
67
+ Args:
68
+ data: Data to serialize
69
+ exclude_none: If True, omit None fields from Pydantic models
70
+
71
+ Returns:
72
+ Any: JSON-serializable data
73
+ """
74
+ if hasattr(data, "model_dump"):
75
+ return data.model_dump(mode="json", exclude_none=exclude_none)
76
+ if isinstance(data, list):
77
+ return [_serialize(item, exclude_none=exclude_none) for item in data]
78
+ return data
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Human-readable formatters (kept from _output.py for backward compat)
83
+ # ---------------------------------------------------------------------------
84
+
85
+
86
+ def print_info(msg: str) -> None:
87
+ """Print an informational message to stderr.
88
+
89
+ Args:
90
+ msg: Message to print
91
+ """
92
+ print(msg, file=sys.stderr)
93
+
94
+
95
+ def print_error(msg: str) -> None:
96
+ """Print an error message to stderr.
97
+
98
+ Args:
99
+ msg: Error message to print
100
+ """
101
+ print(f"Error: {msg}", file=sys.stderr)
102
+
103
+
104
+ def _lifecycle_tag(obj: Any) -> str:
105
+ """Return a lifecycle status tag for objects with a ``status`` attribute.
106
+
107
+ Only emits a tag for non-current statuses (PENDING, ARCHIVED). Objects
108
+ whose status is CURRENT or None are considered active and get no tag.
109
+
110
+ Args:
111
+ obj: Any object that may have a ``status`` attribute
112
+
113
+ Returns:
114
+ str: Tag string like " [PENDING]" or "", including leading space
115
+ """
116
+ status = getattr(obj, "status", None)
117
+ if status is None:
118
+ return ""
119
+ if isinstance(status, str):
120
+ if status.upper() not in ("CURRENT", ""):
121
+ return f" [{status.upper()}]"
122
+ return ""
123
+ # Handle enum-style status where .value is not None
124
+ value = getattr(status, "value", None)
125
+ if value is not None:
126
+ return f" [{str(value).upper()}]"
127
+ return ""
128
+
129
+
130
+ def format_profiles(profiles: list[Any]) -> str:
131
+ """Format profile objects as markdown bullet list.
132
+
133
+ Shows a lifecycle tag ([PENDING]/[ARCHIVED]) when the profile status is
134
+ not CURRENT or None.
135
+
136
+ Args:
137
+ profiles: Profile objects with a profile_content attribute
138
+
139
+ Returns:
140
+ str: Formatted profiles, one per line
141
+ """
142
+ if not profiles:
143
+ return ""
144
+ return "\n".join(f"- {p.content}{_lifecycle_tag(p)}" for p in profiles)
145
+
146
+
147
+ def _structured_lines(playbook: Any) -> list[str]:
148
+ """Build indented structured-data lines from a playbook's top-level fields.
149
+
150
+ Uses the canonical labels: Trigger, Rationale, Blocking Issue.
151
+
152
+ Args:
153
+ playbook: Playbook object with optional trigger, rationale, blocking_issue attrs
154
+
155
+ Returns:
156
+ list[str]: Indented lines (may be empty)
157
+ """
158
+ lines: list[str] = []
159
+ if playbook is None:
160
+ return lines
161
+ if getattr(playbook, "trigger", None):
162
+ lines.append(f" Trigger: {playbook.trigger}")
163
+ if getattr(playbook, "rationale", None):
164
+ lines.append(f" Rationale: {playbook.rationale}")
165
+ return lines
166
+
167
+
168
+ def format_agent_playbooks(agent_playbooks: list[Any]) -> str:
169
+ """Format agent playbook objects with structured fields.
170
+
171
+ Shows an approval status tag ([APPROVED]/[PENDING]/[REJECTED]) from
172
+ ``playbook_status`` and a lifecycle tag ([PENDING]/[ARCHIVED]) from
173
+ ``status`` when not CURRENT/None.
174
+
175
+ Args:
176
+ agent_playbooks: Agent playbook objects with content, trigger, rationale,
177
+ blocking_issue as top-level fields
178
+
179
+ Returns:
180
+ str: Formatted agent playbooks with structured action lines
181
+ """
182
+ if not agent_playbooks:
183
+ return ""
184
+ blocks: list[str] = []
185
+ for playbook in agent_playbooks:
186
+ # Approval status tag from playbook_status
187
+ approval_tag = ""
188
+ ps = getattr(playbook, "playbook_status", None)
189
+ if ps is not None:
190
+ ps_str = str(ps).upper() if not isinstance(ps, str) else ps.upper()
191
+ if ps_str:
192
+ approval_tag = f" [{ps_str}]"
193
+
194
+ lifecycle = _lifecycle_tag(playbook)
195
+ parts = [f"- {playbook.content}{approval_tag}{lifecycle}"]
196
+ parts.extend(_structured_lines(playbook))
197
+ blocks.append("\n".join(parts))
198
+ return "\n\n".join(blocks)
199
+
200
+
201
+ def format_user_playbooks(user_playbooks: list[Any]) -> str:
202
+ """Format user playbook objects with structured fields and source metadata.
203
+
204
+ User playbooks show source/request_id metadata instead of approval status.
205
+ A lifecycle tag ([PENDING]/[ARCHIVED]) is shown when status is not
206
+ CURRENT/None.
207
+
208
+ Args:
209
+ user_playbooks: User playbook objects with content, trigger, rationale,
210
+ blocking_issue as top-level fields, plus source and request_id
211
+
212
+ Returns:
213
+ str: Formatted user playbooks with structured action lines and source
214
+ """
215
+ if not user_playbooks:
216
+ return ""
217
+ blocks: list[str] = []
218
+ for playbook in user_playbooks:
219
+ lifecycle = _lifecycle_tag(playbook)
220
+ parts = [f"- {playbook.content}{lifecycle}"]
221
+ parts.extend(_structured_lines(playbook))
222
+ source = getattr(playbook, "source", None) or "unknown"
223
+ request_id = getattr(playbook, "request_id", None) or "unknown"
224
+ parts.append(f" Source: {source} (request: {request_id})")
225
+ blocks.append("\n".join(parts))
226
+ return "\n\n".join(blocks)
227
+
228
+
229
+ # ---------------------------------------------------------------------------
230
+ # Rich human-readable rendering
231
+ # ---------------------------------------------------------------------------
232
+ #
233
+ # The ``format_*`` functions above return plain text and are used by
234
+ # ``format_context`` (which builds the agent bootstrap context block —
235
+ # pure text, no rich). The ``print_*`` functions below render directly
236
+ # to stdout via a ``rich.console.Console`` so they can wrap long lines,
237
+ # colorize labels, and visually separate entries. ``rich.console.Console``
238
+ # auto-detects whether stdout is a TTY and degrades to plain text when
239
+ # piped, so these are safe to use unconditionally.
240
+
241
+
242
+ def _human_relative_timestamp(ts: int | None) -> str:
243
+ """Render a Unix timestamp as a short relative description.
244
+
245
+ Examples: ``"2m ago"``, ``"3h ago"``, ``"5d ago"``, ``"2026-04-10"``.
246
+ Returns ``"unknown"`` for None or invalid input.
247
+ """
248
+ if not ts:
249
+ return "unknown"
250
+ try:
251
+ from datetime import datetime
252
+
253
+ delta = datetime.now(UTC) - datetime.fromtimestamp(int(ts), tz=UTC)
254
+ seconds = int(delta.total_seconds())
255
+ if seconds < 60:
256
+ return f"{seconds}s ago"
257
+ if seconds < 3600:
258
+ return f"{seconds // 60}m ago"
259
+ if seconds < 86400:
260
+ return f"{seconds // 3600}h ago"
261
+ if seconds < 86400 * 30:
262
+ return f"{seconds // 86400}d ago"
263
+ return datetime.fromtimestamp(int(ts), tz=UTC).strftime("%Y-%m-%d")
264
+ except (TypeError, ValueError, OSError):
265
+ return "unknown"
266
+
267
+
268
+ def print_user_profiles(profiles: list[Any]) -> None:
269
+ """Render user profiles with rich formatting: numbered, wrapped, dimmed metadata.
270
+
271
+ Each profile is shown as a numbered, word-wrapped block. The
272
+ profile content is the primary visual element; source / age /
273
+ lifecycle tag render in a dim metadata footer beneath. When stdout
274
+ is piped, rich auto-detects and emits plain text without colors so
275
+ the output stays grep-friendly.
276
+
277
+ Args:
278
+ profiles: Profile objects with at least ``content``; optional
279
+ ``source``, ``last_modified_timestamp``, ``status``.
280
+ """
281
+ if not profiles:
282
+ return
283
+ from rich.console import Console
284
+ from rich.text import Text
285
+
286
+ console = Console()
287
+ for i, p in enumerate(profiles, 1):
288
+ # Index + content. ``Text.assemble`` keeps the styles separate
289
+ # while wrapping the whole line at the terminal width.
290
+ line = Text.assemble(
291
+ (f"[{i}] ", "bold cyan"),
292
+ getattr(p, "content", "") or "",
293
+ )
294
+ console.print(line)
295
+
296
+ # Dim metadata footer: source, age, lifecycle.
297
+ meta_parts: list[str] = []
298
+ source = getattr(p, "source", None)
299
+ if source:
300
+ meta_parts.append(f"source: {source}")
301
+ ts = getattr(p, "last_modified_timestamp", None)
302
+ if ts:
303
+ meta_parts.append(_human_relative_timestamp(ts))
304
+ lifecycle = _lifecycle_tag(p).strip()
305
+ if lifecycle:
306
+ meta_parts.append(lifecycle)
307
+ if meta_parts:
308
+ console.print(Text(" " + " · ".join(meta_parts), style="dim"))
309
+ console.print() # blank line between entries
310
+
311
+
312
+ def print_user_playbooks(user_playbooks: list[Any]) -> None:
313
+ """Render user playbooks as rich panels with aligned structured fields.
314
+
315
+ Each playbook becomes a bordered panel. The panel title is the
316
+ rule's content (the human-readable summary). The body is a
317
+ two-column grid of the structured fields (Trigger / Rationale /
318
+ Blocking Issue) with right-aligned bold labels and word-wrapped
319
+ values. A dim footer shows source + short request ID.
320
+
321
+ Args:
322
+ user_playbooks: Playbook objects with ``content``,
323
+ ``trigger``, ``rationale``, ``blocking_issue`` as top-level
324
+ fields, plus ``source``, ``request_id``, optional ``status``.
325
+ """
326
+ if not user_playbooks:
327
+ return
328
+ from rich import box
329
+ from rich.console import Console, Group
330
+ from rich.panel import Panel
331
+ from rich.table import Table
332
+ from rich.text import Text
333
+
334
+ console = Console()
335
+ for i, pb in enumerate(user_playbooks, 1):
336
+ # Build a borderless grid for the structured fields. The label
337
+ # column is right-aligned and bold-dim; the value column wraps
338
+ # at the panel width.
339
+ grid = Table.grid(padding=(0, 2), expand=True)
340
+ grid.add_column(style="bold dim", justify="right", no_wrap=True)
341
+ grid.add_column(overflow="fold")
342
+
343
+ for label, attr in (
344
+ ("Trigger", "trigger"),
345
+ ("Rationale", "rationale"),
346
+ ):
347
+ value = getattr(pb, attr, None)
348
+ if value:
349
+ grid.add_row(label, value)
350
+
351
+ # Footer: source + truncated request_id.
352
+ source = getattr(pb, "source", None) or "unknown"
353
+ request_id = getattr(pb, "request_id", None) or "unknown"
354
+ short_req = request_id[:8] if request_id != "unknown" else request_id
355
+ footer = Text(f"source: {source} · request: {short_req}", style="dim")
356
+
357
+ # Content goes inside the panel body, NOT the title — Rich's
358
+ # Panel title doesn't word-wrap and would truncate long rule
359
+ # summaries with a "─" continuation, leaving the most
360
+ # important part of the playbook unreadable. The title stays
361
+ # short (just the index) so the panel border is always tidy.
362
+ content_text = Text(
363
+ getattr(pb, "content", "") or "", style="bold", overflow="fold"
364
+ )
365
+ title = Text.assemble((f"[{i}]", "bold cyan"))
366
+ lifecycle = _lifecycle_tag(pb).strip()
367
+ if lifecycle:
368
+ title.append(f" {lifecycle}", style="yellow")
369
+
370
+ panel = Panel(
371
+ Group(content_text, Text(""), grid, Text(""), footer),
372
+ title=title,
373
+ title_align="left",
374
+ border_style="blue",
375
+ box=box.ROUNDED,
376
+ padding=(1, 2),
377
+ )
378
+ console.print(panel)
379
+ console.print() # spacer between panels
380
+
381
+
382
+ def mask_api_key(api_key: str) -> str:
383
+ """Mask an API key for safe display in command output.
384
+
385
+ Keeps the short provider prefix (``rflx-``, ``sk-``, etc.) and the
386
+ last 4 characters so the user can still distinguish which key is
387
+ active without exposing the full secret. Used by ``reflexio status
388
+ whoami`` and ``reflexio auth status``.
389
+
390
+ Args:
391
+ api_key (str): The raw API key, possibly empty.
392
+
393
+ Returns:
394
+ str: A masked representation safe for printing. Returns
395
+ ``"<unset>"`` for an empty key, ``"*" * len`` for very
396
+ short keys (<=8 chars), and ``"{prefix}****{last4}"``
397
+ for normal keys.
398
+ """
399
+ if not api_key:
400
+ return "<unset>"
401
+ if len(api_key) <= 8:
402
+ return "*" * len(api_key)
403
+ prefix = api_key[:5] if "-" in api_key[:8] else api_key[:4]
404
+ return f"{prefix}****{api_key[-4:]}"
405
+
406
+
407
+ def print_agent_playbooks(agent_playbooks: list[Any]) -> None:
408
+ """Render agent playbooks as rich panels with approval badge.
409
+
410
+ Mirrors :func:`print_user_playbooks` but the title row shows an
411
+ approval badge (``[APPROVED]`` / ``[PENDING]`` / ``[REJECTED]``)
412
+ colour-coded green/yellow/red instead of the source metadata
413
+ footer. The body shows the structured Trigger / Rationale /
414
+ Blocking Issue grid. The footer shows ``playbook_name ·
415
+ agent_version · <relative time>``.
416
+
417
+ Args:
418
+ agent_playbooks: Agent playbook objects with ``content``,
419
+ ``trigger``, ``rationale``, ``blocking_issue`` as top-level
420
+ fields, ``playbook_status``, optional ``playbook_name``,
421
+ ``agent_version``, ``created_at``, ``status``.
422
+ """
423
+ if not agent_playbooks:
424
+ return
425
+ from rich import box
426
+ from rich.console import Console, Group
427
+ from rich.panel import Panel
428
+ from rich.table import Table
429
+ from rich.text import Text
430
+
431
+ badge_styles = {
432
+ "APPROVED": "bold green",
433
+ "PENDING": "bold yellow",
434
+ "REJECTED": "bold red",
435
+ }
436
+
437
+ console = Console()
438
+ for i, pb in enumerate(agent_playbooks, 1):
439
+ grid = Table.grid(padding=(0, 2), expand=True)
440
+ grid.add_column(style="bold dim", justify="right", no_wrap=True)
441
+ grid.add_column(overflow="fold")
442
+
443
+ for label, attr in (
444
+ ("Trigger", "trigger"),
445
+ ("Rationale", "rationale"),
446
+ ):
447
+ value = getattr(pb, attr, None)
448
+ if value:
449
+ grid.add_row(label, value)
450
+
451
+ # Title: [N] + approval badge + lifecycle tag
452
+ title = Text.assemble((f"[{i}]", "bold cyan"))
453
+ ps = getattr(pb, "playbook_status", None)
454
+ if ps is not None:
455
+ ps_str = (ps if isinstance(ps, str) else str(ps)).upper()
456
+ if ps_str:
457
+ style = badge_styles.get(ps_str, "bold white")
458
+ title.append(f" [{ps_str}]", style=style)
459
+ lifecycle = _lifecycle_tag(pb).strip()
460
+ if lifecycle:
461
+ title.append(f" {lifecycle}", style="yellow")
462
+
463
+ # Footer: playbook_name · agent_version · relative created_at
464
+ footer_parts: list[str] = []
465
+ playbook_name = getattr(pb, "playbook_name", None)
466
+ if playbook_name:
467
+ footer_parts.append(str(playbook_name))
468
+ agent_version = getattr(pb, "agent_version", None)
469
+ if agent_version:
470
+ footer_parts.append(f"v{agent_version}")
471
+ created_at = getattr(pb, "created_at", None)
472
+ if created_at:
473
+ footer_parts.append(_human_relative_timestamp(int(created_at)))
474
+
475
+ content_text = Text(
476
+ getattr(pb, "content", "") or "", style="bold", overflow="fold"
477
+ )
478
+ body_parts: list[Any] = [content_text, Text(""), grid]
479
+ if footer_parts:
480
+ body_parts.extend([Text(""), Text(" · ".join(footer_parts), style="dim")])
481
+
482
+ panel = Panel(
483
+ Group(*body_parts),
484
+ title=title,
485
+ title_align="left",
486
+ border_style="magenta",
487
+ box=box.ROUNDED,
488
+ padding=(1, 2),
489
+ )
490
+ console.print(panel)
491
+ console.print()
492
+
493
+
494
+ def print_interactions(interactions: list[Any]) -> None:
495
+ """Render interactions grouped by request_id as rich panels.
496
+
497
+ Each request_id becomes one bordered panel. The title shows the
498
+ short request id, relative timestamp, and turn count. The body is
499
+ a borderless two-column grid with role labels in the left column
500
+ (User cyan, Assistant magenta, System dim, others white) and
501
+ folded content on the right. When stdout is piped, rich
502
+ auto-detects and degrades to plain text.
503
+
504
+ Args:
505
+ interactions: InteractionView-like objects with
506
+ ``request_id``, ``role``, ``content``, ``created_at``.
507
+ """
508
+ if not interactions:
509
+ return
510
+ from collections import defaultdict
511
+
512
+ from rich import box
513
+ from rich.console import Console
514
+ from rich.panel import Panel
515
+ from rich.table import Table
516
+ from rich.text import Text
517
+
518
+ role_styles: dict[str, str] = {
519
+ "user": "bold cyan",
520
+ "assistant": "bold magenta",
521
+ "system": "bold dim",
522
+ }
523
+
524
+ groups: dict[str, list[Any]] = defaultdict(list)
525
+ group_mins: dict[str, int] = {}
526
+ for ix in interactions:
527
+ groups[ix.request_id].append(ix)
528
+ existing = group_mins.get(ix.request_id)
529
+ if existing is None or ix.created_at < existing:
530
+ group_mins[ix.request_id] = ix.created_at
531
+
532
+ # Stable order: earliest group first (O(N) pre-pass above + O(G log G) sort)
533
+ sorted_groups = sorted(groups.items(), key=lambda g: group_mins[g[0]])
534
+
535
+ console = Console()
536
+ for request_id, items in sorted_groups:
537
+ ts = group_mins[request_id]
538
+ try:
539
+ relative = _human_relative_timestamp(int(ts))
540
+ except (TypeError, ValueError):
541
+ relative = "unknown"
542
+ short_id = (request_id or "unknown")[:8]
543
+ turn_count = len(items)
544
+ turn_label = "turn" if turn_count == 1 else "turns"
545
+ title = Text.assemble(
546
+ ("req: ", "dim"),
547
+ (short_id, "bold cyan"),
548
+ (" · ", "dim"),
549
+ (relative, "dim"),
550
+ (" · ", "dim"),
551
+ (f"{turn_count} {turn_label}", "dim"),
552
+ )
553
+
554
+ grid = Table.grid(padding=(0, 2), expand=True)
555
+ grid.add_column(justify="right", no_wrap=True)
556
+ grid.add_column(overflow="fold")
557
+
558
+ for item in sorted(items, key=lambda i: i.created_at):
559
+ role = (item.role or "").strip() or "unknown"
560
+ style = role_styles.get(role.lower(), "bold white")
561
+ grid.add_row(Text(role.capitalize(), style=style), item.content or "")
562
+
563
+ panel = Panel(
564
+ grid,
565
+ title=title,
566
+ title_align="left",
567
+ border_style="blue",
568
+ box=box.ROUNDED,
569
+ padding=(1, 2),
570
+ )
571
+ console.print(panel)
572
+ console.print()
573
+
574
+
575
+ def print_storage_credentials(
576
+ storage_type: str | None,
577
+ storage_config: dict[str, Any],
578
+ *,
579
+ revealed: bool,
580
+ ) -> None:
581
+ """Render the output of ``reflexio config storage`` as a rich table.
582
+
583
+ Prints a bold "Storage type: ..." header followed by a two-column
584
+ grid (field name / value). When the credentials are masked, a
585
+ dim-italic hint reminds the user that ``--reveal`` exists.
586
+
587
+ Args:
588
+ storage_type: The server's storage_type label (e.g.,
589
+ ``"sqlite"``, ``"supabase"``).
590
+ storage_config: The serialized StorageConfig dict (already
591
+ masked or revealed by the caller).
592
+ revealed: Whether ``storage_config`` contains raw credentials.
593
+ When False, a reveal-hint footer is printed.
594
+ """
595
+ from rich.console import Console
596
+ from rich.table import Table
597
+ from rich.text import Text
598
+
599
+ console = Console()
600
+ console.print(
601
+ Text.assemble(
602
+ ("Storage type: ", "bold dim"),
603
+ (storage_type or "unknown", "bold"),
604
+ )
605
+ )
606
+
607
+ grid = Table.grid(padding=(0, 2))
608
+ grid.add_column(style="bold dim", justify="right", no_wrap=True)
609
+ grid.add_column(overflow="fold")
610
+ for key, value in storage_config.items():
611
+ grid.add_row(str(key), "<none>" if value is None else str(value))
612
+ console.print(grid)
613
+
614
+ if not revealed:
615
+ console.print(
616
+ Text(
617
+ "(use --reveal to print raw credential values)",
618
+ style="dim italic",
619
+ )
620
+ )
621
+
622
+
623
+ def print_whoami_summary(
624
+ *,
625
+ endpoint: str,
626
+ api_key: str,
627
+ org_id: str | None,
628
+ storage_type: str | None,
629
+ storage_label: str | None,
630
+ storage_configured: bool,
631
+ message: str | None,
632
+ ) -> None:
633
+ """Render the ``reflexio status whoami`` summary as a rich table.
634
+
635
+ Emits a two-column grid (right-aligned bold-dim label column +
636
+ value column) covering endpoint, masked API key, org id, and
637
+ storage info. The ``[configured]`` / ``[unconfigured]`` marker is
638
+ colour-coded green/yellow. A trailing dim-italic line renders any
639
+ message the server attached to the whoami payload.
640
+
641
+ Args:
642
+ endpoint: Fully-qualified server URL (``client.base_url``).
643
+ api_key: The raw resolved API key — masked internally via
644
+ :func:`mask_api_key`.
645
+ org_id: Organization id from the whoami response, or None.
646
+ storage_type: Storage backend label from the whoami response.
647
+ storage_label: Human-friendly storage name (e.g., a DSN with
648
+ masked credentials), or None.
649
+ storage_configured: Whether the server reports configured
650
+ storage for this org.
651
+ message: Optional advisory message from the whoami response.
652
+ """
653
+ from rich.console import Console
654
+ from rich.table import Table
655
+ from rich.text import Text
656
+
657
+ console = Console()
658
+ grid = Table.grid(padding=(0, 2))
659
+ grid.add_column(style="bold dim", justify="right", no_wrap=True)
660
+ grid.add_column(overflow="fold")
661
+
662
+ grid.add_row("Endpoint", endpoint)
663
+ grid.add_row("API key", mask_api_key(api_key))
664
+ grid.add_row("Org ID", org_id or "<none>")
665
+ if storage_type:
666
+ grid.add_row("Storage type", storage_type)
667
+
668
+ if storage_label:
669
+ marker_text = "[configured]" if storage_configured else "[unconfigured]"
670
+ marker_style = "green" if storage_configured else "yellow"
671
+ storage_value = Text.assemble(
672
+ (storage_label, ""),
673
+ (" ", ""),
674
+ (marker_text, marker_style),
675
+ )
676
+ grid.add_row("Storage", storage_value)
677
+ elif not storage_configured:
678
+ grid.add_row("Storage", Text("<not configured>", style="yellow"))
679
+
680
+ console.print(grid)
681
+
682
+ if message:
683
+ console.print(Text(message, style="dim italic"))
684
+
685
+
686
+ def print_doctor_checks(checks: list[dict[str, Any]]) -> None:
687
+ """Render ``reflexio doctor check`` results as a rich grid.
688
+
689
+ Each check row becomes ``<icon> <name>: <message>`` with the icon
690
+ and name colour-coded by status — green ``✓`` for pass, yellow
691
+ ``⚠`` for warn, red ``✗`` for fail. If a check has a hint, it is
692
+ rendered on an indented second line in dim style.
693
+
694
+ Args:
695
+ checks: Doctor check dicts with keys ``status`` (``"pass"``,
696
+ ``"warn"``, or ``"fail"``), ``name``, ``message``, and
697
+ ``hint`` (possibly None).
698
+ """
699
+ if not checks:
700
+ return
701
+ from rich.console import Console
702
+ from rich.table import Table
703
+ from rich.text import Text
704
+
705
+ icon_styles: dict[str, tuple[str, str]] = {
706
+ "pass": ("✓", "bold green"),
707
+ "warn": ("⚠", "bold yellow"),
708
+ "fail": ("✗", "bold red"),
709
+ }
710
+
711
+ console = Console()
712
+ grid = Table.grid(padding=(0, 1), expand=True)
713
+ grid.add_column(no_wrap=True) # icon
714
+ grid.add_column(no_wrap=True) # name
715
+ grid.add_column(overflow="fold") # message + hint
716
+
717
+ for check in checks:
718
+ status = check.get("status", "warn")
719
+ icon, style = icon_styles.get(status, ("?", "bold white"))
720
+ name = check.get("name", "") or ""
721
+ message = check.get("message", "") or ""
722
+ hint = check.get("hint")
723
+
724
+ body = Text(message)
725
+ if hint:
726
+ body.append("\n")
727
+ body.append(Text(f" Hint: {hint}", style="dim"))
728
+
729
+ grid.add_row(
730
+ Text(icon, style=style),
731
+ Text(f"{name}:", style="bold"),
732
+ body,
733
+ )
734
+ console.print(grid)
735
+
736
+
737
+ def print_auth_status(
738
+ url: str,
739
+ api_key: str,
740
+ env_path: str,
741
+ *,
742
+ env_exists: bool,
743
+ ) -> None:
744
+ """Render ``reflexio auth status`` as a two-column rich grid.
745
+
746
+ Three rows: URL, masked API Key (via :func:`mask_api_key`), and
747
+ env file path. When ``env_exists`` is False, the env file row is
748
+ annotated so the user understands displayed values came from the
749
+ shell environment or defaults rather than the persisted file.
750
+
751
+ Args:
752
+ url: The configured REFLEXIO_URL value, possibly empty.
753
+ api_key: The raw REFLEXIO_API_KEY value, possibly empty.
754
+ env_path: Absolute path to the env file.
755
+ env_exists: Whether the env file currently exists on disk.
756
+ """
757
+ from rich.console import Console
758
+ from rich.table import Table
759
+
760
+ console = Console()
761
+ grid = Table.grid(padding=(0, 2))
762
+ grid.add_column(style="bold dim", justify="right", no_wrap=True)
763
+ grid.add_column(overflow="fold")
764
+ grid.add_row("URL", url or "<unset>")
765
+ grid.add_row("API Key", mask_api_key(api_key))
766
+ env_suffix = "" if env_exists else " (not found — using shell env or defaults)"
767
+ grid.add_row("Env file", f"{env_path}{env_suffix}")
768
+ console.print(grid)
769
+
770
+
771
+ def format_interactions(interactions: list[Any]) -> str:
772
+ """Format interaction objects grouped by request_id as conversation turns.
773
+
774
+ Args:
775
+ interactions: InteractionView objects with request_id, role, content, created_at
776
+
777
+ Returns:
778
+ str: Formatted conversation turns
779
+ """
780
+ if not interactions:
781
+ return ""
782
+
783
+ from collections import defaultdict
784
+ from datetime import datetime
785
+
786
+ groups: dict[str, list[Any]] = defaultdict(list)
787
+ for ix in interactions:
788
+ groups[ix.request_id].append(ix)
789
+
790
+ # Sort groups by earliest created_at
791
+ sorted_groups = sorted(
792
+ groups.items(), key=lambda g: min(i.created_at for i in g[1])
793
+ )
794
+
795
+ blocks: list[str] = []
796
+ for request_id, items in sorted_groups:
797
+ ts = min(i.created_at for i in items)
798
+ try:
799
+ dt = datetime.fromtimestamp(ts, tz=UTC).strftime("%Y-%m-%d %H:%M")
800
+ except (TypeError, ValueError, OSError):
801
+ dt = "unknown"
802
+ short_id = request_id[:8]
803
+ header = f"── {short_id} ({dt} UTC) ──"
804
+ lines = [header]
805
+ for item in sorted(items, key=lambda i: i.created_at):
806
+ role = item.role.capitalize()
807
+ lines.append(f" {role}: {item.content}")
808
+ blocks.append("\n".join(lines))
809
+
810
+ return "\n\n".join(blocks)
811
+
812
+
813
+ def format_context(
814
+ profiles: list[Any],
815
+ agent_playbooks: list[Any],
816
+ user_playbooks: list[Any] | None = None,
817
+ ) -> str:
818
+ """Build a combined context block from profiles, agent playbooks, and user playbooks.
819
+
820
+ Args:
821
+ profiles: Profile objects
822
+ agent_playbooks: Agent playbook objects (org-level behavioral corrections)
823
+ user_playbooks: User playbook objects (user-specific behavioral corrections).
824
+ When provided, rendered after agent playbooks in their own section.
825
+
826
+ Returns:
827
+ str: Combined markdown context block, or empty string if all empty
828
+ """
829
+ sections: list[str] = []
830
+
831
+ if profile_text := format_profiles(profiles):
832
+ sections.append(f"## User Preferences — APPLY THESE\n{profile_text}")
833
+
834
+ if playbook_text := format_agent_playbooks(agent_playbooks):
835
+ sections.append(f"## Behavior Corrections — FOLLOW THESE\n{playbook_text}")
836
+
837
+ if user_playbooks and (user_pb_text := format_user_playbooks(user_playbooks)):
838
+ sections.append(f"## User Behavior Corrections — FOLLOW THESE\n{user_pb_text}")
839
+
840
+ if not sections:
841
+ return ""
842
+
843
+ return (
844
+ "---\n# IMPORTANT: Apply These Corrections (from Reflexio)\n\n"
845
+ + "\n\n".join(sections)
846
+ + "\n\nFailure to follow these means repeating mistakes the user already corrected.\n---"
847
+ )
848
+
849
+
850
+ def pagination_meta(
851
+ items: list[Any],
852
+ limit: int,
853
+ ) -> dict[str, Any]:
854
+ """Build pagination metadata for output envelope.
855
+
856
+ Args:
857
+ items: The items returned
858
+ limit: The requested limit
859
+
860
+ Returns:
861
+ dict: Metadata with count, limit, and has_more flag
862
+ """
863
+ return {
864
+ "count": len(items),
865
+ "limit": limit,
866
+ "has_more": len(items) == limit,
867
+ }