fruxon 0.21.2__tar.gz → 2.0.0__tar.gz

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 (209) hide show
  1. {fruxon-0.21.2 → fruxon-2.0.0}/HISTORY.md +59 -0
  2. fruxon-0.21.2/README.md → fruxon-2.0.0/PKG-INFO +61 -1
  3. fruxon-0.21.2/PKG-INFO → fruxon-2.0.0/README.md +26 -34
  4. {fruxon-0.21.2 → fruxon-2.0.0}/pyproject.toml +13 -0
  5. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/_ssl.py +2 -0
  6. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/_version.py +2 -2
  7. fruxon-2.0.0/src/fruxon/checks/__init__.py +94 -0
  8. fruxon-2.0.0/src/fruxon/checks/agent.py +970 -0
  9. fruxon-2.0.0/src/fruxon/checks/ledger.py +412 -0
  10. fruxon-2.0.0/src/fruxon/checks/references.py +224 -0
  11. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/__init__.py +2 -0
  12. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/_crash.py +19 -16
  13. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/_stream.py +1 -0
  14. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents.py +4 -630
  15. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_draft.py +1 -209
  16. fruxon-2.0.0/src/fruxon/cli/dash.py +120 -0
  17. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/examples.py +9 -0
  18. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/integrations.py +273 -2
  19. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/llm_providers.py +1 -1
  20. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/triggers.py +1 -13
  21. fruxon-2.0.0/src/fruxon/cli/triggers_check.py +123 -0
  22. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/fruxon.py +465 -0
  23. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/models.py +249 -0
  24. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/skills/__init__.py +1 -0
  25. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/skills/fruxon-agent-mode/SKILL.md +1 -0
  26. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/skills/fruxon-meet/SKILL.md +1 -0
  27. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/skills/fruxon-use-integrations/SKILL.md +8 -0
  28. fruxon-2.0.0/src/fruxon/tui/__init__.py +10 -0
  29. fruxon-2.0.0/src/fruxon/tui/app.py +327 -0
  30. fruxon-2.0.0/src/fruxon/tui/demo.py +906 -0
  31. fruxon-2.0.0/src/fruxon/tui/screens/__init__.py +1 -0
  32. fruxon-2.0.0/src/fruxon/tui/screens/agent.py +423 -0
  33. fruxon-2.0.0/src/fruxon/tui/screens/base.py +218 -0
  34. fruxon-2.0.0/src/fruxon/tui/screens/home.py +237 -0
  35. fruxon-2.0.0/src/fruxon/tui/screens/ledger.py +549 -0
  36. fruxon-2.0.0/src/fruxon/tui/screens/pickers.py +111 -0
  37. fruxon-2.0.0/src/fruxon/tui/screens/trace.py +314 -0
  38. fruxon-2.0.0/src/fruxon/tui/source.py +1014 -0
  39. fruxon-2.0.0/src/fruxon/tui/theme.py +203 -0
  40. fruxon-2.0.0/src/fruxon/tui/views.py +306 -0
  41. fruxon-2.0.0/src/fruxon/tui/widgets.py +801 -0
  42. {fruxon-0.21.2 → fruxon-2.0.0}/tests/.test_durations.json +8 -0
  43. fruxon-2.0.0/tests/test_checks_agent.py +236 -0
  44. fruxon-2.0.0/tests/test_checks_ledger.py +256 -0
  45. fruxon-2.0.0/tests/test_dash_app.py +367 -0
  46. fruxon-2.0.0/tests/test_dash_cli.py +106 -0
  47. fruxon-2.0.0/tests/test_dash_client.py +355 -0
  48. fruxon-2.0.0/tests/test_dash_source.py +737 -0
  49. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_docs_commands_exist.py +3 -0
  50. fruxon-2.0.0/tests/test_integration_configs_write_cli.py +414 -0
  51. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_kb_trial_cli.py +3 -1
  52. fruxon-2.0.0/tests/test_triggers_check_cli.py +88 -0
  53. fruxon-2.0.0/tests/test_tui_boundary.py +101 -0
  54. {fruxon-0.21.2 → fruxon-2.0.0}/.gitignore +0 -0
  55. {fruxon-0.21.2 → fruxon-2.0.0}/LICENSE +0 -0
  56. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/__init__.py +0 -0
  57. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/__main__.py +0 -0
  58. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/_schema.py +0 -0
  59. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/_shared.py +0 -0
  60. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_approvals.py +0 -0
  61. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_budget.py +0 -0
  62. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_channels.py +0 -0
  63. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_evaluations.py +0 -0
  64. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_executions.py +0 -0
  65. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_improvements.py +0 -0
  66. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_lifecycle.py +0 -0
  67. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_memory.py +0 -0
  68. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_reply_style.py +0 -0
  69. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_revisions.py +0 -0
  70. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_sandbox.py +0 -0
  71. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_sandbox_test.py +0 -0
  72. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_slots.py +0 -0
  73. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_system_blocks.py +0 -0
  74. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_tests.py +0 -0
  75. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/agents_topics.py +0 -0
  76. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/applications.py +0 -0
  77. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/applications_entry_points.py +0 -0
  78. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/assets.py +0 -0
  79. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/auth.py +0 -0
  80. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/capabilities.py +0 -0
  81. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/completion.py +0 -0
  82. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/config.py +0 -0
  83. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/consult_pins.py +0 -0
  84. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/conversation_imports.py +0 -0
  85. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/describe.py +0 -0
  86. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/doctor.py +0 -0
  87. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/environments.py +0 -0
  88. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/escalations.py +0 -0
  89. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/guides.py +0 -0
  90. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/keys.py +0 -0
  91. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/knowledge_bases.py +0 -0
  92. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/knowledge_documents.py +0 -0
  93. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/knowledge_notices.py +0 -0
  94. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/knowledge_sources.py +0 -0
  95. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/messages.py +0 -0
  96. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/metrics.py +0 -0
  97. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/participants.py +0 -0
  98. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/pipelines.py +0 -0
  99. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/secrets.py +0 -0
  100. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/skills.py +0 -0
  101. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/storage.py +0 -0
  102. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/tools.py +0 -0
  103. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/triggers_budget.py +0 -0
  104. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/triggers_ledger.py +0 -0
  105. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/triggers_ledger_stage_tests.py +0 -0
  106. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/triggers_questions.py +0 -0
  107. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/triggers_shape.py +0 -0
  108. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/cli/workspaces.py +0 -0
  109. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/credentials.py +0 -0
  110. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/device_auth.py +0 -0
  111. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/doctor.py +0 -0
  112. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/exceptions.py +0 -0
  113. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/flow_validation.py +0 -0
  114. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/output.py +0 -0
  115. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/params.py +0 -0
  116. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/sandbox_scenario.py +0 -0
  117. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/skills/fruxon-build-agent/SKILL.md +0 -0
  118. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/skills/fruxon-create-integration/SKILL.md +0 -0
  119. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/skills/fruxon-debug-trace/SKILL.md +0 -0
  120. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/telemetry.py +0 -0
  121. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/ui.py +0 -0
  122. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/update_check.py +0 -0
  123. {fruxon-0.21.2 → fruxon-2.0.0}/src/fruxon/validation.py +0 -0
  124. {fruxon-0.21.2 → fruxon-2.0.0}/tests/__init__.py +0 -0
  125. {fruxon-0.21.2 → fruxon-2.0.0}/tests/conftest.py +0 -0
  126. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_actor.py +0 -0
  127. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_agent_lifecycle.py +0 -0
  128. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_agent_revisions_list.py +0 -0
  129. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_agent_slots.py +0 -0
  130. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_agents_check_cli.py +0 -0
  131. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_agents_revisions_cli.py +0 -0
  132. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_agents_sandbox.py +0 -0
  133. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_applications.py +0 -0
  134. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_approvals.py +0 -0
  135. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_assets.py +0 -0
  136. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_budgets.py +0 -0
  137. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_capabilities.py +0 -0
  138. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_channels.py +0 -0
  139. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_cli.py +0 -0
  140. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_cli_base_url_routing.py +0 -0
  141. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_cli_crash.py +0 -0
  142. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_cli_root_flags.py +0 -0
  143. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_client.py +0 -0
  144. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_connect_nudge.py +0 -0
  145. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_consult.py +0 -0
  146. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_conversation_imports.py +0 -0
  147. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_credentials.py +0 -0
  148. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_describe.py +0 -0
  149. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_device_auth.py +0 -0
  150. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_doctor.py +0 -0
  151. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_draft_bootstrap.py +0 -0
  152. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_draft_evaluate_cli.py +0 -0
  153. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_draft_pull_shape.py +0 -0
  154. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_draft_run_replay.py +0 -0
  155. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_draft_validate_cli.py +0 -0
  156. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_drafts.py +0 -0
  157. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_entry_points.py +0 -0
  158. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_environments.py +0 -0
  159. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_escalations.py +0 -0
  160. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_evaluations.py +0 -0
  161. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_execution_records.py +0 -0
  162. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_flow_validation.py +0 -0
  163. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_fruxon.py +0 -0
  164. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_guides.py +0 -0
  165. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_improvements.py +0 -0
  166. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_integration_triggers_cli.py +0 -0
  167. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_integrations_authorize_cli.py +0 -0
  168. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_knowledge_bases.py +0 -0
  169. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_knowledge_documents.py +0 -0
  170. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_knowledge_notices.py +0 -0
  171. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_knowledge_sources.py +0 -0
  172. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_llm_provider_configs_cli.py +0 -0
  173. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_managed_by.py +0 -0
  174. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_memory.py +0 -0
  175. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_messages.py +0 -0
  176. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_metrics.py +0 -0
  177. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_online_llm_config_refs.py +0 -0
  178. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_online_prefetch_refs.py +0 -0
  179. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_online_secret_refs.py +0 -0
  180. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_output.py +0 -0
  181. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_params.py +0 -0
  182. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_participants.py +0 -0
  183. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_participants_write.py +0 -0
  184. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_pipelines.py +0 -0
  185. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_reply_style.py +0 -0
  186. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_sandbox_scenario.py +0 -0
  187. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_schema.py +0 -0
  188. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_schema_fetch.py +0 -0
  189. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_secrets.py +0 -0
  190. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_skills.py +0 -0
  191. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_ssl.py +0 -0
  192. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_storage.py +0 -0
  193. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_system_blocks.py +0 -0
  194. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_system_blocks_cli.py +0 -0
  195. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_telemetry.py +0 -0
  196. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_test_chats.py +0 -0
  197. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_topics.py +0 -0
  198. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_trigger_budgets.py +0 -0
  199. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_triggers.py +0 -0
  200. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_triggers_ledger.py +0 -0
  201. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_triggers_ledger_batch_views.py +0 -0
  202. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_triggers_ledger_stage_tests.py +0 -0
  203. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_triggers_questions.py +0 -0
  204. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_triggers_shape.py +0 -0
  205. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_triggers_write.py +0 -0
  206. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_ui.py +0 -0
  207. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_update_check.py +0 -0
  208. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_validation.py +0 -0
  209. {fruxon-0.21.2 → fruxon-2.0.0}/tests/test_workspaces_cli.py +0 -0
@@ -1,5 +1,64 @@
1
1
  # History
2
2
 
3
+ ## Unreleased — `fruxon triggers check`, and an automation's Configuration tab in `fruxon dash`
4
+
5
+ - **`fruxon triggers check <trigger> [-o json]`** audits a work-shape
6
+ automation part by part: the automation, its source (by its last pass),
7
+ every stage (a PROCESS stage's agent runs and reaches what it calls; an ASK
8
+ stage has someone to ask and is not close to expiring questions), the
9
+ outcome note's contact, the weekly budget and the dead items. Same
10
+ `{…, overall, checks[]}` contract and exit codes as `agents check`.
11
+ The audit is `fruxon.checks.ledger.check_ledger`.
12
+ - `fruxon dash`: an automation's `2` Configuration tab shows those rows with
13
+ the selected one explained in place, `t` tests the source (admits nothing),
14
+ `p` previews what a stage sends its agent (saves nothing), and lists the
15
+ shape.
16
+
17
+ ## Unreleased — `fruxon dash`, a read-only console for an Application
18
+
19
+ Everything else in the CLI prints and exits. `fruxon dash` stays open and
20
+ shows one Application the way the web app's overview does, in the terminal.
21
+
22
+ - **`fruxon dash [--app A] [--demo] [--ground black|ink]`**. Home: runs,
23
+ spend, items finished and who is waiting in big digits; every agent and
24
+ automation; what needs a person. An agent: overview, the `agents check`
25
+ rows plus credential, model-config and budget health, runs and traces. An
26
+ automation: its stages and counts, items per day, what failed, its items.
27
+ Live parts every 5 s, totals every minute. Read-only.
28
+ - Needs the new `tui` extra: `pip install "fruxon[tui]"` (Textual). Nothing
29
+ imports it until the command runs, so the CLI starts as fast as before.
30
+ - `--demo` is a made-up workspace with a pinned clock and seeded live runs,
31
+ for recordings; `docs/tui/demo.tape` records it with VHS.
32
+ - The audit behind `agents check` moved to `fruxon.checks.agent` and returns
33
+ `CheckResult` rows; the command's output and exit codes are unchanged.
34
+ - New client reads: `get_application_spend`, `get_application_activity_counts`,
35
+ `get_application_support_kpis`, `get_agent_conversation_timeseries`,
36
+ `get_agent_judge_activity`, `get_agent_cost_forecast`,
37
+ `get_agent_llm_config_health`, `get_agent_credential_health`.
38
+
39
+ ## Unreleased — edit and publish an integration config from the CLI
40
+
41
+ `integrations configs` could only `list` and `get`, so changing a config
42
+ (clearing Agentit's BAFI egress pin, 2026-10-19) meant the dashboard. Uses the
43
+ backend's existing `PATCH …/configs/{config}` and `POST …/configs/{config}:publish`.
44
+
45
+ - **`fruxon integrations configs update <integration> <config> --file <patch.json>`**
46
+ patches the config's draft. The file holds only the fields to change
47
+ (`{"egressCountry": ""}` clears an egress pin); `--schema` prints the shape.
48
+ The server ignores fields it doesn't know and reads `null` as "unchanged",
49
+ so an empty patch, an unknown field or a `null` exits 12 before anything is
50
+ sent. Prints the config with secrets masked by the server, never the values
51
+ sent.
52
+ - **`fruxon integrations configs publish <integration> <config> [--note T]`**
53
+ makes the draft the revision tool calls resolve. Shows what is live now and
54
+ asks; `--yes` skips it and is required in agent mode (exit 16 without).
55
+ Prints the new revision's `id` and `version`.
56
+ - `FruxonClient.update_integration_config(integration, config_id, body)` and
57
+ `publish_integration_config(integration, config_id, note=None)`. The
58
+ publish response is the snapshot with its credentials decrypted; the client
59
+ returns only `id`, `configId`, `version`, `note`, `sandboxMode`,
60
+ `egressCountry`, `createdBy` and `createdAt`.
61
+
3
62
  ## Unreleased — telemetry names the command, the person and the workspace
4
63
 
5
64
  CLI usage in Mixpanel had three gaps. The command name stopped at two
@@ -1,3 +1,38 @@
1
+ Metadata-Version: 2.5
2
+ Name: fruxon
3
+ Version: 2.0.0
4
+ Summary: The Fruxon SDK is a lightweight Python client for integrating with the Fruxon platform.
5
+ Project-URL: bugs, https://github.com/fruxon-ai/fruxon-sdk/issues
6
+ Project-URL: changelog, https://github.com/fruxon-ai/fruxon-sdk/blob/main/HISTORY.md
7
+ Project-URL: homepage, https://github.com/fruxon-ai/fruxon-sdk
8
+ Author-email: Hagai Cohen <hagai@fruxon.com>
9
+ Maintainer-email: Hagai Cohen <hagai@fruxon.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: certifi>=2024.2.2
22
+ Requires-Dist: click>=8.2
23
+ Requires-Dist: keyring>=24.0
24
+ Requires-Dist: typer>=0.13
25
+ Provides-Extra: test
26
+ Requires-Dist: coverage; extra == 'test'
27
+ Requires-Dist: ipdb; extra == 'test'
28
+ Requires-Dist: pre-commit; extra == 'test'
29
+ Requires-Dist: pytest; extra == 'test'
30
+ Requires-Dist: ruff; extra == 'test'
31
+ Requires-Dist: ty; extra == 'test'
32
+ Provides-Extra: tui
33
+ Requires-Dist: textual>=8.2; extra == 'tui'
34
+ Description-Content-Type: text/markdown
35
+
1
36
  # fruxon
2
37
 
3
38
  [![PyPI version](https://img.shields.io/pypi/v/fruxon.svg)](https://pypi.org/project/fruxon/)
@@ -106,7 +141,7 @@ Integrations + tools:
106
141
  | Command | What it does |
107
142
  | --- | --- |
108
143
  | `fruxon integrations list/get/create/update/verify/open` | Manage external integrations — the connections agents draw tools from. `create`/`update`/`verify` take a `--file` JSON body; `open` opens the dashboard. |
109
- | `fruxon integrations configs list/get` | Inspect the per-integration auth/config records. |
144
+ | `fruxon integrations configs list/get/update/publish` | The per-integration auth/config records. `update --file` patches a config's draft (only the fields sent; `{"egressCountry": ""}` clears an egress pin) and prints it with secrets masked; `publish` makes the draft the revision tool calls resolve, asks first (`--yes` in agent mode), and prints the new revision's `id` and `version`, never the credentials. |
110
145
  | `fruxon integrations authorize <integration>` | Mint an application-level OAuth authorization URL to **connect** an integration — auto-detects its OAuth2 method (`--auth-method` to pick, `--scope`/`--config-param` to tune). OAuth needs a browser consent step, so the CLI hands you the link; a human clicks it, and the connection is saved as a tenant config a slot can pin. |
111
146
  | `fruxon integrations triggers <integration>` | List the event types an integration can fire an agent on — each descriptor's `id` is the `eventType` a trigger listens for, plus the `payloadFields` (dotted paths) a binding's `parameterMappings` can read. Discovery for wiring an inbound-event trigger without guessing. |
112
147
  | `fruxon integrations mcp …` | Inspect and enable the MCP server for an integration. |
@@ -130,6 +165,7 @@ Integrations + tools:
130
165
  | `fruxon triggers ledger funnel/items/cursor/transitions/effects/batches` | The work items a schedule trigger's work shape admitted. `funnel` counts per stage and status (every item is in exactly one cell). `items` is the grid — `--stage`, `--status`, `--ignore-reason`, `--item-key` (client-side; the endpoint has no such filter), each item's `label` (what the Work grid calls the row, as against the provider's opaque `itemKey`), and its accumulated `data`, which is where a stage's declared `produces` keys land. A listing is **one page of 20** that reports how many matched in total — a ledger's healthy state is thousands of rows served ten per request, so an unbounded walk is rate-limited long before the end; `-n` sizes the page and `--all` walks the lot. `--ignore-reason` narrows to the items one exclusion reason set aside — the same filter `redrive-many` selects by, matched whole and case-sensitively, so it is how you read a batch before putting it back. `--where field=value` (or `!=`, repeatable, dotted for a nested field) selects by what a stage wrote into `data` — `--where destination=SKIP` — matched whole and case-sensitively, as text and as the number, boolean or null it spells; the server filters and the CLI re-applies it, so an older server is slower, never wrong. `cursor` is the watermark and its freshness. `transitions` is one item's attempt history; `effects` is what it did to the outside world — an `UNKNOWN` row parks the item until someone settles it. `batches` is what an ASK stage is waiting on a human for — read its `delivery` first, because `UNDELIVERABLE` / `FAILED` mean nobody was asked and nobody will answer while the batch, the count and the stage all look healthy (`QUEUE_ONLY` is not a fault); a WAITING item's `batchId` is the join to it, and the pairing is total, so one `--open-only` listing resolves every waiting row. `batches --answers <batch>` is one question's answers item by item — what the person gave (`given`), what was handed back (`answer`), what the item holds now (`current`), with `mismatch` where the first two differ (a prose reply is every item's `given` at once, `givenIsReply`, and is never compared); a `given` the server did not record is read from the resolving run's sheet read (`givenFrom: "trace"`, `--no-trace` to skip). `batches --transcript <batch>` prints the conversation it was asked in, read under the agent that owns it — the stage's asking agent, not the desk that talks to the person. `funnel` and `items` also carry `cost` (all-in USD) and `runs` — so the funnel says *which stage* the money goes to, not merely how much. An item's `runs` spans every stage it passed through and so is not `stageAttempts`, which resets per stage; a `Cost` of `—` means nothing there has ever been claimed, as against `$0.0000`, which means it ran and was free. Cost is not sortable: the endpoint's `--order-by` allow-list does not carry it. |
131
166
  | `fruxon triggers ledger passes` | Every time the automation fired, newest first — `admitted` (what the door took), `dispatched` (what the pass claimed and sent to work, questions parked on a human included), `completed` (how many of *this pass's* dispatches reached the terminal stage), `runs`, and the pass's all-in USD. `runs` sits **below** `dispatched` while runs are in flight or when a dispatch went to a person rather than a model — that gap is normal, not a lost item. And a pass's `cost` **rises after** it fired, as those runs finish, so a pass read seconds later reports a floor rather than its bill. An `outcome` other than `FIRED` means no sweep happened: `FILTERED` / `COALESCED` are healthy (nothing to do, or folded into a pass already running), `FAILED` / `FILTER_ERROR` are not. `-n/--limit` bounds the walk. |
132
167
  | `fruxon triggers ledger spend/spend-series/item-series` | What the automation spent and what happened to its items, over time. `spend` is the **current UTC calendar** day, week (from Monday) or month (`--period`), so a weekly read on a Monday is hours of spend, not seven days — the figure the automation's spend chip shows: item runs, the conversations its questions are held in, and its report, one row per agent and part (`ITEM_RUNS` / `CONVERSATIONS` / `REPORTS` / `OTHER`). It is **not** the funnel's `cost`, which files item runs under the cell each item sits in *now*; the two overlap, so never add them. `spend-series` is the same figure per `day` or `week` over `--since`/`--until` (default the last 30 days), `--by-stage` adding a column per agent and part; it reads live run records, so a window older than the workspace's run history is counted from `countedFromMs` and the CLI says so. `--interval hour` is refused (tokens are priced per day), and a `week` bucket starts on a Thursday, so it does not line up with `spend --period weekly`. `item-series` counts admitted, finished, set aside and dead per `hour`, `day` or `week` — **events, not items**: a re-driven item that finishes again counts twice and an operator's ignore counts as set aside. Both series omit empty buckets; the footer says how many had anything. A server that predates the series reads 404s (exit 11). |
168
+ | `fruxon triggers check <trigger>` | Audit whether a work-shape automation can move its items, part by part in the order an item meets them: on and taking items in, the last pass read its source, each PROCESS stage's agent exists with a deployed revision and working credentials and models (a rejected credential's row says how many items failed there since), each ASK stage's contact is declared and bound on the asking agent and its oldest question has not waited past a fifth of its answer window, the outcome note's contact reaches a person, this week's spend against the weekly limit, and how many items died in the last 7 days. `{trigger, overall, checks[]}` like `agents check`; exits 12 only on a hard fail, warnings stay 0. Read-only. |
133
169
  | `fruxon triggers budget list/get/set/delete` | Spend limits on an automation **as a whole** (`--period DAILY/WEEKLY/MONTHLY`, UTC calendar windows), counting every production run it caused whichever agent made it. Once one is enforced they govern its runs, which then meet only these limits and not the agents' own; with none enforced, each stage stops at its agent's own limit. None by default and every one removable. An enforced limit refuses new work at the limit (stage runs, passes, a question's opening, the report) and holds the stages until the window resets, while turns of conversations it already opened still run (up to 110%); `--no-enforce` only alerts the Application's admins. Needs Editor. |
134
170
  | `fruxon triggers ledger redrive/ignore/settle/cancel-batch` | The operator verbs; all confirm and refuse without `--yes` in agent mode. `redrive` returns a DEAD or FAILED item to the queue with its attempts reset — settle any `UNKNOWN` effect first or the next attempt hits the same refusal. `ignore` excludes an item for a **required** reason, which is the only thing separating IGNORED from DEAD later. `settle` answers an effect the platform could not confirm (`--landed` / `--did-not-land` + `--reason`); wrong in the second direction duplicates the write. `cancel-batch` calls off a question — the items go back to READY and a later pass re-batches them. |
135
171
  | `fruxon triggers ledger set-data` | Correct a parked (DEAD / FAILED / IGNORED) item's `data` before re-driving it — the value a stage recorded wrong, such as an ASK answer written as `SKIP`, which a plain re-drive would route on again. `--set path=value` (or `path:=<json>`, repeatable) on a top-level key the item carries or a path the shape declares (`produces`, `answeredWhenPath`); a misspelled key is refused with the settable list rather than added. `--reason` is required and lands on the item's history beside every old → new value. `--redrive` re-drives in the same transaction; `redrive` and `redrive-many` take `--set` too. |
@@ -157,6 +193,30 @@ Integrations + tools:
157
193
  | `fruxon agents channels list` / `fruxon agents endpoints` | Inspect a networked agent's channel bindings and resolved provider endpoints (with bot identity). Superseded by `applications entry-points list`, which names the Application answering on each address; these read endpoints outside the published API. |
158
194
  | `fruxon skills list/show/create/update/delete` | Browse the tenant's product-skill catalog and author its own skills (resources that attach to agents at runtime). |
159
195
 
196
+ ### `fruxon dash` — the console
197
+
198
+ ![fruxon dash --demo](https://raw.githubusercontent.com/fruxon-ai/fruxon-sdk/develop/docs/tui/demo.gif)
199
+
200
+ A read-only, full-screen console for one Application. Home has the week's runs,
201
+ spend and finished items, every agent with its share of the spend, every
202
+ automation with what it holds, and what needs a person; Enter opens an agent
203
+ (overview, the `agents check` rows plus credential, model and budget health,
204
+ runs and their traces) or an automation (its stages, items per day, what
205
+ failed, the `triggers check` rows, a source test and a stage preview). Live
206
+ parts refresh every 5 seconds, totals every minute. It changes nothing.
207
+
208
+ ```bash
209
+ pip install "fruxon[tui]" # the console runs on Textual, an optional extra
210
+ fruxon dash # the Application with the most agents
211
+ fruxon dash --app "Support & Ops"
212
+ fruxon dash --demo # a made-up workspace, for a first look or a recording
213
+ ```
214
+
215
+ Keys: `↑↓` move, `enter` open, `tab` next panel, `esc` back, `1 2 3` tabs,
216
+ `7 3 1` (or `w` on a tabbed screen) the time window, `a` another Application,
217
+ `r` refresh, `?` every key, `q` quit. It needs a terminal, so agent mode and
218
+ piped output are refused (exit 16).
219
+
160
220
  ### `fruxon agents draft run` — examples
161
221
 
162
222
  ```bash
@@ -1,36 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: fruxon
3
- Version: 0.21.2
4
- Summary: The Fruxon SDK is a lightweight Python client for integrating with the Fruxon platform.
5
- Project-URL: bugs, https://github.com/fruxon-ai/fruxon-sdk/issues
6
- Project-URL: changelog, https://github.com/fruxon-ai/fruxon-sdk/blob/main/HISTORY.md
7
- Project-URL: homepage, https://github.com/fruxon-ai/fruxon-sdk
8
- Author-email: Hagai Cohen <hagai@fruxon.com>
9
- Maintainer-email: Hagai Cohen <hagai@fruxon.com>
10
- License: MIT
11
- License-File: LICENSE
12
- Classifier: Development Status :: 3 - Alpha
13
- Classifier: Intended Audience :: Developers
14
- Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.13
20
- Requires-Python: >=3.10
21
- Requires-Dist: certifi>=2024.2.2
22
- Requires-Dist: click>=8.2
23
- Requires-Dist: keyring>=24.0
24
- Requires-Dist: typer>=0.13
25
- Provides-Extra: test
26
- Requires-Dist: coverage; extra == 'test'
27
- Requires-Dist: ipdb; extra == 'test'
28
- Requires-Dist: pre-commit; extra == 'test'
29
- Requires-Dist: pytest; extra == 'test'
30
- Requires-Dist: ruff; extra == 'test'
31
- Requires-Dist: ty; extra == 'test'
32
- Description-Content-Type: text/markdown
33
-
34
1
  # fruxon
35
2
 
36
3
  [![PyPI version](https://img.shields.io/pypi/v/fruxon.svg)](https://pypi.org/project/fruxon/)
@@ -139,7 +106,7 @@ Integrations + tools:
139
106
  | Command | What it does |
140
107
  | --- | --- |
141
108
  | `fruxon integrations list/get/create/update/verify/open` | Manage external integrations — the connections agents draw tools from. `create`/`update`/`verify` take a `--file` JSON body; `open` opens the dashboard. |
142
- | `fruxon integrations configs list/get` | Inspect the per-integration auth/config records. |
109
+ | `fruxon integrations configs list/get/update/publish` | The per-integration auth/config records. `update --file` patches a config's draft (only the fields sent; `{"egressCountry": ""}` clears an egress pin) and prints it with secrets masked; `publish` makes the draft the revision tool calls resolve, asks first (`--yes` in agent mode), and prints the new revision's `id` and `version`, never the credentials. |
143
110
  | `fruxon integrations authorize <integration>` | Mint an application-level OAuth authorization URL to **connect** an integration — auto-detects its OAuth2 method (`--auth-method` to pick, `--scope`/`--config-param` to tune). OAuth needs a browser consent step, so the CLI hands you the link; a human clicks it, and the connection is saved as a tenant config a slot can pin. |
144
111
  | `fruxon integrations triggers <integration>` | List the event types an integration can fire an agent on — each descriptor's `id` is the `eventType` a trigger listens for, plus the `payloadFields` (dotted paths) a binding's `parameterMappings` can read. Discovery for wiring an inbound-event trigger without guessing. |
145
112
  | `fruxon integrations mcp …` | Inspect and enable the MCP server for an integration. |
@@ -163,6 +130,7 @@ Integrations + tools:
163
130
  | `fruxon triggers ledger funnel/items/cursor/transitions/effects/batches` | The work items a schedule trigger's work shape admitted. `funnel` counts per stage and status (every item is in exactly one cell). `items` is the grid — `--stage`, `--status`, `--ignore-reason`, `--item-key` (client-side; the endpoint has no such filter), each item's `label` (what the Work grid calls the row, as against the provider's opaque `itemKey`), and its accumulated `data`, which is where a stage's declared `produces` keys land. A listing is **one page of 20** that reports how many matched in total — a ledger's healthy state is thousands of rows served ten per request, so an unbounded walk is rate-limited long before the end; `-n` sizes the page and `--all` walks the lot. `--ignore-reason` narrows to the items one exclusion reason set aside — the same filter `redrive-many` selects by, matched whole and case-sensitively, so it is how you read a batch before putting it back. `--where field=value` (or `!=`, repeatable, dotted for a nested field) selects by what a stage wrote into `data` — `--where destination=SKIP` — matched whole and case-sensitively, as text and as the number, boolean or null it spells; the server filters and the CLI re-applies it, so an older server is slower, never wrong. `cursor` is the watermark and its freshness. `transitions` is one item's attempt history; `effects` is what it did to the outside world — an `UNKNOWN` row parks the item until someone settles it. `batches` is what an ASK stage is waiting on a human for — read its `delivery` first, because `UNDELIVERABLE` / `FAILED` mean nobody was asked and nobody will answer while the batch, the count and the stage all look healthy (`QUEUE_ONLY` is not a fault); a WAITING item's `batchId` is the join to it, and the pairing is total, so one `--open-only` listing resolves every waiting row. `batches --answers <batch>` is one question's answers item by item — what the person gave (`given`), what was handed back (`answer`), what the item holds now (`current`), with `mismatch` where the first two differ (a prose reply is every item's `given` at once, `givenIsReply`, and is never compared); a `given` the server did not record is read from the resolving run's sheet read (`givenFrom: "trace"`, `--no-trace` to skip). `batches --transcript <batch>` prints the conversation it was asked in, read under the agent that owns it — the stage's asking agent, not the desk that talks to the person. `funnel` and `items` also carry `cost` (all-in USD) and `runs` — so the funnel says *which stage* the money goes to, not merely how much. An item's `runs` spans every stage it passed through and so is not `stageAttempts`, which resets per stage; a `Cost` of `—` means nothing there has ever been claimed, as against `$0.0000`, which means it ran and was free. Cost is not sortable: the endpoint's `--order-by` allow-list does not carry it. |
164
131
  | `fruxon triggers ledger passes` | Every time the automation fired, newest first — `admitted` (what the door took), `dispatched` (what the pass claimed and sent to work, questions parked on a human included), `completed` (how many of *this pass's* dispatches reached the terminal stage), `runs`, and the pass's all-in USD. `runs` sits **below** `dispatched` while runs are in flight or when a dispatch went to a person rather than a model — that gap is normal, not a lost item. And a pass's `cost` **rises after** it fired, as those runs finish, so a pass read seconds later reports a floor rather than its bill. An `outcome` other than `FIRED` means no sweep happened: `FILTERED` / `COALESCED` are healthy (nothing to do, or folded into a pass already running), `FAILED` / `FILTER_ERROR` are not. `-n/--limit` bounds the walk. |
165
132
  | `fruxon triggers ledger spend/spend-series/item-series` | What the automation spent and what happened to its items, over time. `spend` is the **current UTC calendar** day, week (from Monday) or month (`--period`), so a weekly read on a Monday is hours of spend, not seven days — the figure the automation's spend chip shows: item runs, the conversations its questions are held in, and its report, one row per agent and part (`ITEM_RUNS` / `CONVERSATIONS` / `REPORTS` / `OTHER`). It is **not** the funnel's `cost`, which files item runs under the cell each item sits in *now*; the two overlap, so never add them. `spend-series` is the same figure per `day` or `week` over `--since`/`--until` (default the last 30 days), `--by-stage` adding a column per agent and part; it reads live run records, so a window older than the workspace's run history is counted from `countedFromMs` and the CLI says so. `--interval hour` is refused (tokens are priced per day), and a `week` bucket starts on a Thursday, so it does not line up with `spend --period weekly`. `item-series` counts admitted, finished, set aside and dead per `hour`, `day` or `week` — **events, not items**: a re-driven item that finishes again counts twice and an operator's ignore counts as set aside. Both series omit empty buckets; the footer says how many had anything. A server that predates the series reads 404s (exit 11). |
133
+ | `fruxon triggers check <trigger>` | Audit whether a work-shape automation can move its items, part by part in the order an item meets them: on and taking items in, the last pass read its source, each PROCESS stage's agent exists with a deployed revision and working credentials and models (a rejected credential's row says how many items failed there since), each ASK stage's contact is declared and bound on the asking agent and its oldest question has not waited past a fifth of its answer window, the outcome note's contact reaches a person, this week's spend against the weekly limit, and how many items died in the last 7 days. `{trigger, overall, checks[]}` like `agents check`; exits 12 only on a hard fail, warnings stay 0. Read-only. |
166
134
  | `fruxon triggers budget list/get/set/delete` | Spend limits on an automation **as a whole** (`--period DAILY/WEEKLY/MONTHLY`, UTC calendar windows), counting every production run it caused whichever agent made it. Once one is enforced they govern its runs, which then meet only these limits and not the agents' own; with none enforced, each stage stops at its agent's own limit. None by default and every one removable. An enforced limit refuses new work at the limit (stage runs, passes, a question's opening, the report) and holds the stages until the window resets, while turns of conversations it already opened still run (up to 110%); `--no-enforce` only alerts the Application's admins. Needs Editor. |
167
135
  | `fruxon triggers ledger redrive/ignore/settle/cancel-batch` | The operator verbs; all confirm and refuse without `--yes` in agent mode. `redrive` returns a DEAD or FAILED item to the queue with its attempts reset — settle any `UNKNOWN` effect first or the next attempt hits the same refusal. `ignore` excludes an item for a **required** reason, which is the only thing separating IGNORED from DEAD later. `settle` answers an effect the platform could not confirm (`--landed` / `--did-not-land` + `--reason`); wrong in the second direction duplicates the write. `cancel-batch` calls off a question — the items go back to READY and a later pass re-batches them. |
168
136
  | `fruxon triggers ledger set-data` | Correct a parked (DEAD / FAILED / IGNORED) item's `data` before re-driving it — the value a stage recorded wrong, such as an ASK answer written as `SKIP`, which a plain re-drive would route on again. `--set path=value` (or `path:=<json>`, repeatable) on a top-level key the item carries or a path the shape declares (`produces`, `answeredWhenPath`); a misspelled key is refused with the settable list rather than added. `--reason` is required and lands on the item's history beside every old → new value. `--redrive` re-drives in the same transaction; `redrive` and `redrive-many` take `--set` too. |
@@ -190,6 +158,30 @@ Integrations + tools:
190
158
  | `fruxon agents channels list` / `fruxon agents endpoints` | Inspect a networked agent's channel bindings and resolved provider endpoints (with bot identity). Superseded by `applications entry-points list`, which names the Application answering on each address; these read endpoints outside the published API. |
191
159
  | `fruxon skills list/show/create/update/delete` | Browse the tenant's product-skill catalog and author its own skills (resources that attach to agents at runtime). |
192
160
 
161
+ ### `fruxon dash` — the console
162
+
163
+ ![fruxon dash --demo](https://raw.githubusercontent.com/fruxon-ai/fruxon-sdk/develop/docs/tui/demo.gif)
164
+
165
+ A read-only, full-screen console for one Application. Home has the week's runs,
166
+ spend and finished items, every agent with its share of the spend, every
167
+ automation with what it holds, and what needs a person; Enter opens an agent
168
+ (overview, the `agents check` rows plus credential, model and budget health,
169
+ runs and their traces) or an automation (its stages, items per day, what
170
+ failed, the `triggers check` rows, a source test and a stage preview). Live
171
+ parts refresh every 5 seconds, totals every minute. It changes nothing.
172
+
173
+ ```bash
174
+ pip install "fruxon[tui]" # the console runs on Textual, an optional extra
175
+ fruxon dash # the Application with the most agents
176
+ fruxon dash --app "Support & Ops"
177
+ fruxon dash --demo # a made-up workspace, for a first look or a recording
178
+ ```
179
+
180
+ Keys: `↑↓` move, `enter` open, `tab` next panel, `esc` back, `1 2 3` tabs,
181
+ `7 3 1` (or `w` on a tabbed screen) the time window, `a` another Application,
182
+ `r` refresh, `?` every key, `q` quit. It needs a terminal, so agent mode and
183
+ piped output are refused (exit 16).
184
+
193
185
  ### `fruxon agents draft run` — examples
194
186
 
195
187
  ```bash
@@ -92,6 +92,12 @@ dependencies = [
92
92
  requires-python = ">= 3.10"
93
93
 
94
94
  [project.optional-dependencies]
95
+ # ``fruxon dash`` — the read-only, full-screen console. Kept out of the base
96
+ # install so ``pip install fruxon`` stays as small as it was and the CLI's
97
+ # start-up never pays for it: the console imports Textual only when it runs.
98
+ tui = [
99
+ "textual>=8.2",
100
+ ]
95
101
  test = [
96
102
  "coverage", # testing
97
103
  "pytest", # testing
@@ -117,6 +123,13 @@ fruxon = "fruxon.cli:app"
117
123
  [tool.ruff]
118
124
  line-length = 120
119
125
 
126
+ [tool.ruff.format]
127
+ # Ruff 0.17 formats the Python code blocks inside Markdown too. Ours are
128
+ # illustrations, not code: hand-aligned comments, one-line calls and
129
+ # pseudo-signatures (``def f(...): ... # POST /route``) that the formatter
130
+ # would explode. Leave the docs as written.
131
+ exclude = ["*.md"]
132
+
120
133
  [tool.ruff.lint]
121
134
  select = [
122
135
  "E", # pycodestyle errors
@@ -52,6 +52,7 @@ def context_for(base_url: str) -> ssl.SSLContext | None:
52
52
  """
53
53
  host = urllib.parse.urlparse(base_url).hostname or ""
54
54
  if host in _LOOPBACK_HOSTS:
55
+ # nosemgrep: unverified-ssl-context — loopback hosts only; the request never leaves this machine
55
56
  return ssl._create_unverified_context() # noqa: S323 — loopback only
56
57
 
57
58
  bundle = os.environ.get("FRUXON_CA_BUNDLE", "").strip()
@@ -62,6 +63,7 @@ def context_for(base_url: str) -> ssl.SSLContext | None:
62
63
 
63
64
  if _insecure_enabled():
64
65
  _warn_insecure_once(host)
66
+ # nosemgrep: unverified-ssl-context — explicit FRUXON_INSECURE=1 opt-in, warned about on stderr
65
67
  return ssl._create_unverified_context() # noqa: S323 — opt-in
66
68
 
67
69
  return _default_or_certifi_context()
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.21.2'
22
- __version_tuple__ = version_tuple = (0, 21, 2)
21
+ __version__ = version = '2.0.0'
22
+ __version_tuple__ = version_tuple = (2, 0, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,94 @@
1
+ """Read-only audits shared by the CLI and the ``fruxon dash`` console.
2
+
3
+ A check reads a resource and the surfaces around it and says, row by row,
4
+ whether it is wired to run. The rows are plain data: :class:`CheckResult`
5
+ carries no markup and decides nothing about how it is shown, so
6
+ ``fruxon agents check`` prints them as text or JSON and the console lays
7
+ them out in a panel, and both say the same thing because they ran the same
8
+ code.
9
+
10
+ Nothing here writes. Nothing here imports :mod:`fruxon.cli`, either — the
11
+ console is built without the CLI, and a check that reached into it would
12
+ drag the whole command tree in with it.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass, field
18
+
19
+ # The four outcomes a row can have. ``skip`` is "nothing to check here" (no
20
+ # consult step, so no roster to audit), which is a pass, not a warning.
21
+ STATUSES = ("ok", "warn", "fail", "skip")
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class CheckResult:
26
+ """One audited surface: what was checked, how it came out, and what to do.
27
+
28
+ ``detail`` is what was found, in a sentence. ``hint`` is the next step —
29
+ usually a CLI command — or ``None`` when there is nothing to do.
30
+ ``explain`` says what the row checks and why it matters, for a reader who
31
+ has never seen it before; the CLI does not print it, the console shows it
32
+ beside the selected row.
33
+ """
34
+
35
+ title: str
36
+ status: str
37
+ detail: str
38
+ hint: str | None = None
39
+ explain: str = ""
40
+
41
+ def to_dict(self) -> dict:
42
+ """The row as ``agents check -o json`` has always printed it."""
43
+ return {"title": self.title, "status": self.status, "detail": self.detail, "hint": self.hint}
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class CheckReport:
48
+ """Every row of one audit, plus what the console needs to draw around it.
49
+
50
+ ``revision`` is the revision the audit read (``None`` when there is
51
+ none). ``revision_body`` is that revision as fetched, kept so a caller
52
+ that wants to draw the agent's flow does not have to fetch it again.
53
+ """
54
+
55
+ subject: str
56
+ checks: tuple[CheckResult, ...] = ()
57
+ revision: int | None = None
58
+ revision_body: dict | None = field(default=None, compare=False)
59
+
60
+ @property
61
+ def overall(self) -> str:
62
+ """``fail`` if any row failed, else ``warn`` if any warned, else ``ok``."""
63
+ return overall_status(self.checks)
64
+
65
+ def counts(self) -> dict[str, int]:
66
+ """How many rows passed (``ok`` and ``skip``), warned and failed."""
67
+ return {
68
+ "passed": sum(1 for c in self.checks if c.status in ("ok", "skip")),
69
+ "warnings": sum(1 for c in self.checks if c.status == "warn"),
70
+ "failing": sum(1 for c in self.checks if c.status == "fail"),
71
+ }
72
+
73
+
74
+ def overall_status(checks) -> str:
75
+ """The worst status among ``checks``: ``fail`` › ``warn`` › ``ok``."""
76
+ statuses = {c.status for c in checks}
77
+ if "fail" in statuses:
78
+ return "fail"
79
+ if "warn" in statuses:
80
+ return "warn"
81
+ return "ok"
82
+
83
+
84
+ def shape_get(node: dict, name: str) -> object:
85
+ """Read a work-shape key by camelCase name, tolerating PascalCase.
86
+
87
+ The API speaks camelCase, but the stored JSONB is PascalCase — so a
88
+ shape pasted out of the database (or a stage file hand-written against
89
+ it) reads the other way. Only the display path is tolerant; what gets
90
+ posted back is whatever the caller supplied, untouched.
91
+ """
92
+ if name in node:
93
+ return node[name]
94
+ return node.get(name[:1].upper() + name[1:])