fruxon 0.21.2__tar.gz → 1.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 (181) hide show
  1. {fruxon-0.21.2 → fruxon-1.0.0}/HISTORY.md +23 -0
  2. {fruxon-0.21.2 → fruxon-1.0.0}/PKG-INFO +2 -2
  3. {fruxon-0.21.2 → fruxon-1.0.0}/README.md +1 -1
  4. {fruxon-0.21.2 → fruxon-1.0.0}/pyproject.toml +7 -0
  5. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/_version.py +2 -2
  6. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/_crash.py +19 -16
  7. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/examples.py +9 -0
  8. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/integrations.py +273 -2
  9. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/fruxon.py +69 -0
  10. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/skills/fruxon-agent-mode/SKILL.md +1 -0
  11. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/skills/fruxon-meet/SKILL.md +1 -0
  12. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/skills/fruxon-use-integrations/SKILL.md +8 -0
  13. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_docs_commands_exist.py +3 -0
  14. fruxon-1.0.0/tests/test_integration_configs_write_cli.py +414 -0
  15. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_kb_trial_cli.py +3 -1
  16. {fruxon-0.21.2 → fruxon-1.0.0}/.gitignore +0 -0
  17. {fruxon-0.21.2 → fruxon-1.0.0}/LICENSE +0 -0
  18. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/__init__.py +0 -0
  19. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/__main__.py +0 -0
  20. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/_ssl.py +0 -0
  21. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/__init__.py +0 -0
  22. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/_schema.py +0 -0
  23. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/_shared.py +0 -0
  24. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/_stream.py +0 -0
  25. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents.py +0 -0
  26. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_approvals.py +0 -0
  27. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_budget.py +0 -0
  28. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_channels.py +0 -0
  29. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_draft.py +0 -0
  30. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_evaluations.py +0 -0
  31. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_executions.py +0 -0
  32. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_improvements.py +0 -0
  33. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_lifecycle.py +0 -0
  34. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_memory.py +0 -0
  35. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_reply_style.py +0 -0
  36. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_revisions.py +0 -0
  37. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_sandbox.py +0 -0
  38. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_sandbox_test.py +0 -0
  39. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_slots.py +0 -0
  40. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_system_blocks.py +0 -0
  41. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_tests.py +0 -0
  42. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/agents_topics.py +0 -0
  43. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/applications.py +0 -0
  44. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/applications_entry_points.py +0 -0
  45. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/assets.py +0 -0
  46. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/auth.py +0 -0
  47. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/capabilities.py +0 -0
  48. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/completion.py +0 -0
  49. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/config.py +0 -0
  50. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/consult_pins.py +0 -0
  51. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/conversation_imports.py +0 -0
  52. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/describe.py +0 -0
  53. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/doctor.py +0 -0
  54. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/environments.py +0 -0
  55. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/escalations.py +0 -0
  56. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/guides.py +0 -0
  57. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/keys.py +0 -0
  58. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/knowledge_bases.py +0 -0
  59. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/knowledge_documents.py +0 -0
  60. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/knowledge_notices.py +0 -0
  61. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/knowledge_sources.py +0 -0
  62. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/llm_providers.py +0 -0
  63. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/messages.py +0 -0
  64. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/metrics.py +0 -0
  65. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/participants.py +0 -0
  66. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/pipelines.py +0 -0
  67. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/secrets.py +0 -0
  68. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/skills.py +0 -0
  69. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/storage.py +0 -0
  70. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/tools.py +0 -0
  71. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/triggers.py +0 -0
  72. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/triggers_budget.py +0 -0
  73. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/triggers_ledger.py +0 -0
  74. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/triggers_ledger_stage_tests.py +0 -0
  75. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/triggers_questions.py +0 -0
  76. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/triggers_shape.py +0 -0
  77. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/cli/workspaces.py +0 -0
  78. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/credentials.py +0 -0
  79. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/device_auth.py +0 -0
  80. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/doctor.py +0 -0
  81. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/exceptions.py +0 -0
  82. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/flow_validation.py +0 -0
  83. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/models.py +0 -0
  84. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/output.py +0 -0
  85. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/params.py +0 -0
  86. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/sandbox_scenario.py +0 -0
  87. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/skills/__init__.py +0 -0
  88. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/skills/fruxon-build-agent/SKILL.md +0 -0
  89. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/skills/fruxon-create-integration/SKILL.md +0 -0
  90. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/skills/fruxon-debug-trace/SKILL.md +0 -0
  91. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/telemetry.py +0 -0
  92. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/ui.py +0 -0
  93. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/update_check.py +0 -0
  94. {fruxon-0.21.2 → fruxon-1.0.0}/src/fruxon/validation.py +0 -0
  95. {fruxon-0.21.2 → fruxon-1.0.0}/tests/.test_durations.json +0 -0
  96. {fruxon-0.21.2 → fruxon-1.0.0}/tests/__init__.py +0 -0
  97. {fruxon-0.21.2 → fruxon-1.0.0}/tests/conftest.py +0 -0
  98. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_actor.py +0 -0
  99. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_agent_lifecycle.py +0 -0
  100. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_agent_revisions_list.py +0 -0
  101. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_agent_slots.py +0 -0
  102. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_agents_check_cli.py +0 -0
  103. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_agents_revisions_cli.py +0 -0
  104. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_agents_sandbox.py +0 -0
  105. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_applications.py +0 -0
  106. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_approvals.py +0 -0
  107. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_assets.py +0 -0
  108. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_budgets.py +0 -0
  109. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_capabilities.py +0 -0
  110. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_channels.py +0 -0
  111. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_cli.py +0 -0
  112. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_cli_base_url_routing.py +0 -0
  113. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_cli_crash.py +0 -0
  114. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_cli_root_flags.py +0 -0
  115. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_client.py +0 -0
  116. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_connect_nudge.py +0 -0
  117. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_consult.py +0 -0
  118. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_conversation_imports.py +0 -0
  119. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_credentials.py +0 -0
  120. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_describe.py +0 -0
  121. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_device_auth.py +0 -0
  122. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_doctor.py +0 -0
  123. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_draft_bootstrap.py +0 -0
  124. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_draft_evaluate_cli.py +0 -0
  125. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_draft_pull_shape.py +0 -0
  126. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_draft_run_replay.py +0 -0
  127. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_draft_validate_cli.py +0 -0
  128. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_drafts.py +0 -0
  129. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_entry_points.py +0 -0
  130. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_environments.py +0 -0
  131. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_escalations.py +0 -0
  132. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_evaluations.py +0 -0
  133. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_execution_records.py +0 -0
  134. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_flow_validation.py +0 -0
  135. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_fruxon.py +0 -0
  136. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_guides.py +0 -0
  137. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_improvements.py +0 -0
  138. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_integration_triggers_cli.py +0 -0
  139. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_integrations_authorize_cli.py +0 -0
  140. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_knowledge_bases.py +0 -0
  141. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_knowledge_documents.py +0 -0
  142. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_knowledge_notices.py +0 -0
  143. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_knowledge_sources.py +0 -0
  144. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_llm_provider_configs_cli.py +0 -0
  145. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_managed_by.py +0 -0
  146. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_memory.py +0 -0
  147. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_messages.py +0 -0
  148. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_metrics.py +0 -0
  149. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_online_llm_config_refs.py +0 -0
  150. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_online_prefetch_refs.py +0 -0
  151. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_online_secret_refs.py +0 -0
  152. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_output.py +0 -0
  153. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_params.py +0 -0
  154. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_participants.py +0 -0
  155. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_participants_write.py +0 -0
  156. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_pipelines.py +0 -0
  157. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_reply_style.py +0 -0
  158. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_sandbox_scenario.py +0 -0
  159. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_schema.py +0 -0
  160. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_schema_fetch.py +0 -0
  161. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_secrets.py +0 -0
  162. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_skills.py +0 -0
  163. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_ssl.py +0 -0
  164. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_storage.py +0 -0
  165. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_system_blocks.py +0 -0
  166. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_system_blocks_cli.py +0 -0
  167. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_telemetry.py +0 -0
  168. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_test_chats.py +0 -0
  169. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_topics.py +0 -0
  170. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_trigger_budgets.py +0 -0
  171. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_triggers.py +0 -0
  172. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_triggers_ledger.py +0 -0
  173. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_triggers_ledger_batch_views.py +0 -0
  174. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_triggers_ledger_stage_tests.py +0 -0
  175. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_triggers_questions.py +0 -0
  176. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_triggers_shape.py +0 -0
  177. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_triggers_write.py +0 -0
  178. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_ui.py +0 -0
  179. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_update_check.py +0 -0
  180. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_validation.py +0 -0
  181. {fruxon-0.21.2 → fruxon-1.0.0}/tests/test_workspaces_cli.py +0 -0
@@ -1,5 +1,28 @@
1
1
  # History
2
2
 
3
+ ## Unreleased — edit and publish an integration config from the CLI
4
+
5
+ `integrations configs` could only `list` and `get`, so changing a config
6
+ (clearing Agentit's BAFI egress pin, 2026-10-19) meant the dashboard. Uses the
7
+ backend's existing `PATCH …/configs/{config}` and `POST …/configs/{config}:publish`.
8
+
9
+ - **`fruxon integrations configs update <integration> <config> --file <patch.json>`**
10
+ patches the config's draft. The file holds only the fields to change
11
+ (`{"egressCountry": ""}` clears an egress pin); `--schema` prints the shape.
12
+ The server ignores fields it doesn't know and reads `null` as "unchanged",
13
+ so an empty patch, an unknown field or a `null` exits 12 before anything is
14
+ sent. Prints the config with secrets masked by the server, never the values
15
+ sent.
16
+ - **`fruxon integrations configs publish <integration> <config> [--note T]`**
17
+ makes the draft the revision tool calls resolve. Shows what is live now and
18
+ asks; `--yes` skips it and is required in agent mode (exit 16 without).
19
+ Prints the new revision's `id` and `version`.
20
+ - `FruxonClient.update_integration_config(integration, config_id, body)` and
21
+ `publish_integration_config(integration, config_id, note=None)`. The
22
+ publish response is the snapshot with its credentials decrypted; the client
23
+ returns only `id`, `configId`, `version`, `note`, `sandboxMode`,
24
+ `egressCountry`, `createdBy` and `createdAt`.
25
+
3
26
  ## Unreleased — telemetry names the command, the person and the workspace
4
27
 
5
28
  CLI usage in Mixpanel had three gaps. The command name stopped at two
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fruxon
3
- Version: 0.21.2
3
+ Version: 1.0.0
4
4
  Summary: The Fruxon SDK is a lightweight Python client for integrating with the Fruxon platform.
5
5
  Project-URL: bugs, https://github.com/fruxon-ai/fruxon-sdk/issues
6
6
  Project-URL: changelog, https://github.com/fruxon-ai/fruxon-sdk/blob/main/HISTORY.md
@@ -139,7 +139,7 @@ Integrations + tools:
139
139
  | Command | What it does |
140
140
  | --- | --- |
141
141
  | `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. |
142
+ | `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
143
  | `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
144
  | `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
145
  | `fruxon integrations mcp …` | Inspect and enable the MCP server for an integration. |
@@ -106,7 +106,7 @@ Integrations + tools:
106
106
  | Command | What it does |
107
107
  | --- | --- |
108
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. |
109
- | `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. |
110
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. |
111
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. |
112
112
  | `fruxon integrations mcp …` | Inspect and enable the MCP server for an integration. |
@@ -117,6 +117,13 @@ fruxon = "fruxon.cli:app"
117
117
  [tool.ruff]
118
118
  line-length = 120
119
119
 
120
+ [tool.ruff.format]
121
+ # Ruff 0.17 formats the Python code blocks inside Markdown too. Ours are
122
+ # illustrations, not code: hand-aligned comments, one-line calls and
123
+ # pseudo-signatures (``def f(...): ... # POST /route``) that the formatter
124
+ # would explode. Leave the docs as written.
125
+ exclude = ["*.md"]
126
+
120
127
  [tool.ruff.lint]
121
128
  select = [
122
129
  "E", # pycodestyle errors
@@ -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 = '1.0.0'
22
+ __version_tuple__ = version_tuple = (1, 0, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -72,8 +72,9 @@ def is_success_exit(exc: BaseException) -> bool:
72
72
  """Whether Click/Typer stopped successfully after help or version.
73
73
 
74
74
  ``--help`` and ``--version`` use a zero-code ``Exit`` instead of returning
75
- normally. Match both Click namespaces because current Typer vendors its
76
- own copy while older releases use the top-level package.
75
+ normally. Which class that is depends on the Typer release: the top-level
76
+ Click one, the vendored ``typer._click`` copy (0.26), or ``typer.exceptions``
77
+ (0.27, which moved ``Exit`` and ``Abort`` out of the vendored Click).
77
78
  """
78
79
  return _is_click_exception(exc, class_name="Exit") and getattr(exc, "exit_code", 1) == 0
79
80
 
@@ -84,22 +85,24 @@ def is_no_args_help(exc: BaseException) -> bool:
84
85
 
85
86
 
86
87
  def _is_click_exception(exc: BaseException, *, class_name: str = "ClickException") -> bool:
87
- """Match Click's public exceptions and Typer's vendored equivalents."""
88
- import click
88
+ """Match Click's public exceptions and Typer's vendored equivalents.
89
89
 
90
- classes: list[type[BaseException]] = []
91
- external = getattr(click.exceptions, class_name, None)
92
- if isinstance(external, type) and issubclass(external, BaseException):
93
- classes.append(external)
94
-
95
- try:
96
- from typer._click import exceptions as typer_click_exceptions
90
+ Typer 0.27 keeps ``Exit`` and ``Abort`` in :mod:`typer.exceptions` rather
91
+ than in its vendored Click, so a ``--help`` raised there matched neither
92
+ Click namespace and was reported as a failed command.
93
+ """
94
+ import importlib
97
95
 
98
- vendored = getattr(typer_click_exceptions, class_name, None)
99
- if isinstance(vendored, type) and issubclass(vendored, BaseException):
100
- classes.append(vendored)
101
- except ImportError: # Older Typer releases use the top-level Click package.
102
- pass
96
+ classes: list[type[BaseException]] = []
97
+ # Older Typer releases have neither of the last two and use top-level Click.
98
+ for module_name in ("click.exceptions", "typer._click.exceptions", "typer.exceptions"):
99
+ try:
100
+ module = importlib.import_module(module_name)
101
+ except ImportError:
102
+ continue
103
+ found = getattr(module, class_name, None)
104
+ if isinstance(found, type) and issubclass(found, BaseException):
105
+ classes.append(found)
103
106
 
104
107
  return bool(classes) and isinstance(exc, tuple(classes))
105
108
 
@@ -700,6 +700,15 @@ _EXAMPLES: dict[str, list[tuple[str, str]]] = {
700
700
  ("fruxon integrations create -f config.json", "Wire up a new external service"),
701
701
  ("fruxon integrations open my-integration", "Open the dashboard config page"),
702
702
  ("fruxon integrations verify my-integration -f auth.json", "Test credentials"),
703
+ ("fruxon integrations configs list my-integration", "Saved credentials — the tenantConfigId a slot pins"),
704
+ (
705
+ 'echo \'{"egressCountry": ""}\' | fruxon integrations configs update my-integration e71e… -f -',
706
+ "Patch a config's draft (only the fields you send)",
707
+ ),
708
+ (
709
+ "fruxon integrations configs publish my-integration e71e… --note '…'",
710
+ "Make the draft live for every tool call",
711
+ ),
703
712
  ],
704
713
  "tools": [
705
714
  ("fruxon tools list my-integration", "List tools under an integration"),
@@ -11,6 +11,10 @@ Read commands (``list`` / ``get``) take simple flags; write commands
11
11
  because the payloads (``CreateIntegration``, ``VerifyAuthConfigRequest``)
12
12
  carry structured ``configMetadata`` / ``authConfig`` objects, not
13
13
  key=value pairs — same pattern as ``fruxon agents test``.
14
+
15
+ ``configs`` covers a tenant's saved credentials for an integration:
16
+ ``configs update`` patches a config's draft from a ``--file`` JSON body,
17
+ and ``configs publish`` ships the draft as the revision tool calls resolve.
14
18
  """
15
19
 
16
20
  from __future__ import annotations
@@ -19,9 +23,10 @@ import json
19
23
  from typing import Annotated
20
24
 
21
25
  import typer
26
+ from rich.markup import escape
22
27
 
23
28
  from fruxon.cli import app
24
- from fruxon.cli._shared import build_client, load_json_body, print_connect_nudge
29
+ from fruxon.cli._shared import build_client, confirm_or_die, load_json_body, print_connect_nudge
25
30
  from fruxon.exceptions import FruxonError
26
31
  from fruxon.fruxon import Integration
27
32
  from fruxon.ui import (
@@ -30,10 +35,12 @@ from fruxon.ui import (
30
35
  fail_from_api_error,
31
36
  integration_connect_url,
32
37
  print_banner,
38
+ require_yes_in_agent_mode,
33
39
  resolve_output_format,
34
40
  say_err,
35
41
  say_info,
36
42
  say_ok,
43
+ say_warn,
37
44
  stderr,
38
45
  )
39
46
 
@@ -674,7 +681,7 @@ def integrations_authorize(
674
681
  # ─────────────────────────────────────────────────────────────────────────────
675
682
 
676
683
  configs_app = typer.Typer(
677
- help="List and inspect tenant-level integration configs (reusable auth + connection settings).",
684
+ help="List, inspect, update and publish tenant-level integration configs (reusable auth + connection settings).",
678
685
  no_args_is_help=True,
679
686
  )
680
687
  integrations_app.add_typer(configs_app, name="configs")
@@ -781,6 +788,270 @@ def integration_configs_get(
781
788
  print(json.dumps(body, indent=2))
782
789
 
783
790
 
791
+ # The top-level fields of ``UpdateTenantIntegrationConfigRequest``. The server
792
+ # drops a key it doesn't know without a word, so a typo (``egress_country``)
793
+ # would "succeed" and change nothing; checking here turns that into an error.
794
+ # It binds case-insensitively, so this matches that way too.
795
+ _CONFIG_UPDATE_FIELDS = (
796
+ "displayName",
797
+ "parameters",
798
+ "auth",
799
+ "authMap",
800
+ "sandboxMode",
801
+ "sandboxVariant",
802
+ "egressCountry",
803
+ "oAuthCredentialKey",
804
+ )
805
+ _PUBLISH_CONFIG_ACTION = "publish an integration config"
806
+
807
+
808
+ @configs_app.command("update")
809
+ def integration_configs_update(
810
+ integration: Annotated[str, typer.Argument(help="Integration the config belongs to.")],
811
+ config_id: Annotated[str, typer.Argument(help="Config UUID to update.")],
812
+ file: Annotated[
813
+ str | None,
814
+ typer.Option(
815
+ "--file",
816
+ "-f",
817
+ help="UpdateTenantIntegrationConfigRequest JSON body: only the fields to change. `-` reads stdin.",
818
+ ),
819
+ ] = None,
820
+ schema: Annotated[
821
+ bool,
822
+ typer.Option(
823
+ "--schema", help="Print the JSON schema for --file (UpdateTenantIntegrationConfigRequest) and exit."
824
+ ),
825
+ ] = False,
826
+ workspace: Annotated[str | None, typer.Option("--workspace", help="Workspace identifier override.")] = None,
827
+ base_url: Annotated[str | None, typer.Option("--base-url", help="API base URL override.")] = None,
828
+ output: Annotated[
829
+ str | None,
830
+ typer.Option("--output", "-o", help="Output format: text (default for humans), json (default in agent mode)."),
831
+ ] = None,
832
+ ):
833
+ """Update a config's draft from a JSON patch. Nothing changes at run time until `publish`.
834
+
835
+ `--file` holds only the fields to change. Fields you leave out keep their
836
+ saved values; `parameters`, `auth` and `authMap` replace the saved value
837
+ whole when present, so copy them from `configs get` and send a secret's
838
+ masked value back unchanged to keep it. `"egressCountry": ""` clears the
839
+ egress pin; `null` means "leave unchanged", so it is refused here rather
840
+ than silently doing nothing.
841
+
842
+ Tool calls resolve the config's *published* revision, not its draft:
843
+ follow with `configs publish` to make the change live.
844
+
845
+ Prints the updated config with secrets masked by the server, the same
846
+ shape as `configs get` — never the values you sent. Prefer the dashboard
847
+ for entering new credentials: a secret in `--file` sits on disk.
848
+
849
+ Needs the `integrations:write` scope.
850
+
851
+ Examples:
852
+ echo '{"egressCountry": ""}' | fruxon integrations configs update bafi <config-id> --file -
853
+ fruxon integrations configs update github <config-id> --file ./patch.json
854
+ fruxon integrations configs update github <config-id> --schema > schema.json
855
+ """
856
+ from fruxon.cli._schema import print_schema
857
+
858
+ if schema:
859
+ print_schema("UpdateTenantIntegrationConfigRequest", workspace=workspace, base_url=base_url)
860
+ return
861
+ if not file:
862
+ fail(
863
+ "Missing required option [bold]--file[/bold].",
864
+ hint="Pass a JSON file with the fields to change, or [bold]--schema[/bold] to see the shape.",
865
+ code=EXIT_VALIDATION,
866
+ )
867
+
868
+ output = _resolve_write_output(output)
869
+ body = load_json_body(file, what="an UpdateTenantIntegrationConfigRequest object (the fields to change)")
870
+ _check_config_patch(body)
871
+
872
+ client = build_client(workspace, base_url)
873
+ try:
874
+ config = client.update_integration_config(integration, config_id, body)
875
+ except FruxonError as e:
876
+ fail_from_api_error(e, hint=_hint_for_config_error(e, integration))
877
+
878
+ publish_cmd = f"fruxon integrations configs publish {integration} {config_id}"
879
+ published = config.get("publishedRevisionId")
880
+ not_live = (
881
+ f"Not live yet: tool calls still resolve published revision {published}."
882
+ if published
883
+ else "This config has never been published, so no tool call resolves it yet."
884
+ )
885
+ if output == "json":
886
+ print(json.dumps(config, indent=2))
887
+ stderr.print(f"[dim]Draft updated. {not_live} Publish with: {publish_cmd}[/dim]")
888
+ return
889
+
890
+ name = config.get("displayName") or config_id
891
+ if stderr.is_terminal:
892
+ print_banner(subtitle=f"config · {integration}")
893
+ stderr.print()
894
+ say_ok(
895
+ f"Updated the draft of [bold]{escape(name)}[/bold] ({config_id}) · changed: {', '.join(body)}",
896
+ hint=f"{not_live} Publish with [bold]{publish_cmd}[/bold].",
897
+ )
898
+
899
+
900
+ @configs_app.command("publish")
901
+ def integration_configs_publish(
902
+ integration: Annotated[str, typer.Argument(help="Integration the config belongs to.")],
903
+ config_id: Annotated[str, typer.Argument(help="Config UUID to publish.")],
904
+ note: Annotated[
905
+ str | None,
906
+ typer.Option("--note", help="Publish note stored on the new revision (e.g. 'cleared egress pin')."),
907
+ ] = None,
908
+ yes: Annotated[
909
+ bool,
910
+ typer.Option("--yes", "-y", help="Skip the confirmation prompt. REQUIRED under agent mode (CLAUDECODE / CI)."),
911
+ ] = False,
912
+ workspace: Annotated[str | None, typer.Option("--workspace", help="Workspace identifier override.")] = None,
913
+ base_url: Annotated[str | None, typer.Option("--base-url", help="API base URL override.")] = None,
914
+ output: Annotated[
915
+ str | None,
916
+ typer.Option("--output", "-o", help="Output format: text (default for humans), json (default in agent mode)."),
917
+ ] = None,
918
+ ):
919
+ """Publish a config's draft as a new revision. Tool calls switch to it immediately.
920
+
921
+ Snapshots the draft (parameters, auth, authMap, sandbox routing, egress
922
+ country) into a new, immutable revision and points the config's
923
+ `publishedRevisionId` at it. Every agent and trigger resolving this config
924
+ uses the new revision from its next tool call. To revert, roll back to an
925
+ earlier revision on the config's dashboard page.
926
+
927
+ Prints the new revision's `id` and `version` (plus `configId`, `note`,
928
+ `sandboxMode`, `egressCountry`, `createdBy`, `createdAt`). The server's
929
+ response also carries the snapshotted credentials; they are dropped, never
930
+ printed.
931
+
932
+ Asks for confirmation; `--yes` skips it and is required in agent mode.
933
+ Needs the `integrations:write` scope.
934
+
935
+ Examples:
936
+ fruxon integrations configs publish bafi <config-id> --note 'cleared egress pin'
937
+ fruxon integrations configs publish github <config-id> --yes -o json
938
+ """
939
+ output = _resolve_write_output(output)
940
+ # Before any network call: an agent without --yes can't be asked.
941
+ require_yes_in_agent_mode(yes, action=_PUBLISH_CONFIG_ACTION)
942
+ client = build_client(workspace, base_url)
943
+ # Read it first, so the prompt names the config and what is live now —
944
+ # and so a wrong id fails before anyone is asked to confirm anything.
945
+ try:
946
+ config = client.get_integration_config(integration, config_id)
947
+ except FruxonError as e:
948
+ fail_from_api_error(e, hint=_hint_for_config_error(e, integration))
949
+
950
+ name = config.get("displayName") or config_id
951
+ if output == "text" or not yes:
952
+ if output == "text" and stderr.is_terminal:
953
+ print_banner(subtitle=f"config publish · {integration}")
954
+ stderr.print()
955
+ _render_config_publish_preview(config, integration, config_id)
956
+
957
+ confirm_or_die(
958
+ f"Publish the draft of [bold]{escape(name)}[/bold] as a new revision? "
959
+ "Every tool call using this config switches to it immediately.",
960
+ yes=yes,
961
+ action=_PUBLISH_CONFIG_ACTION,
962
+ )
963
+
964
+ try:
965
+ if stderr.is_terminal and output == "text":
966
+ with stderr.status(f"[bold]Publishing [cyan]{escape(name)}[/cyan]…[/bold]"):
967
+ revision = client.publish_integration_config(integration, config_id, note=note)
968
+ else:
969
+ revision = client.publish_integration_config(integration, config_id, note=note)
970
+ except FruxonError as e:
971
+ fail_from_api_error(e, hint=_hint_for_config_error(e, integration))
972
+
973
+ if output == "json":
974
+ print(json.dumps(revision, indent=2))
975
+ return
976
+
977
+ version = revision.get("version")
978
+ stderr.print()
979
+ say_ok(
980
+ f"Published [bold]{escape(name)}[/bold] ({config_id}) as revision "
981
+ f"[bold]v{version if version is not None else '?'}[/bold] [dim]{revision.get('id') or '—'}[/dim]",
982
+ hint=(
983
+ "Tool calls resolving this config use it from their next call. "
984
+ "To revert, roll back to an earlier revision on the config's dashboard page."
985
+ ),
986
+ )
987
+
988
+
989
+ def _check_config_patch(body: dict) -> None:
990
+ """Refuse a patch the server would accept and then ignore, before sending it."""
991
+ known = {field.lower() for field in _CONFIG_UPDATE_FIELDS}
992
+ fields = ", ".join(_CONFIG_UPDATE_FIELDS)
993
+ if not body:
994
+ fail(
995
+ "Nothing to update: the patch is an empty object.",
996
+ hint=f'Send only the fields to change, e.g. {{"egressCountry": ""}}. Fields: {fields}.',
997
+ code=EXIT_VALIDATION,
998
+ )
999
+ unknown = [key for key in body if key.lower() not in known]
1000
+ if unknown:
1001
+ them = "them" if len(unknown) != 1 else "it"
1002
+ fail(
1003
+ f"Unknown field(s) in the patch: {escape(', '.join(unknown))}.",
1004
+ hint=(
1005
+ f"The server would ignore {them} and change nothing. Fields: {fields}. "
1006
+ "Read-only fields from `configs get` (id, hasAuth, …) don't belong here."
1007
+ ),
1008
+ code=EXIT_VALIDATION,
1009
+ )
1010
+ nulls = [key for key, value in body.items() if value is None]
1011
+ if nulls:
1012
+ them = "them" if len(nulls) != 1 else "it"
1013
+ clear = ' Send "" to clear egressCountry.' if "egresscountry" in {key.lower() for key in nulls} else ""
1014
+ fail(
1015
+ f"null leaves a field unchanged, so this would do nothing for: {escape(', '.join(nulls))}.",
1016
+ hint=f"Drop {them} from the patch.{clear}",
1017
+ code=EXIT_VALIDATION,
1018
+ )
1019
+
1020
+
1021
+ def _render_config_publish_preview(config: dict, integration: str, config_id: str) -> None:
1022
+ """What is about to be published, and what is live now."""
1023
+ say_info(f"[bold]{escape(config.get('displayName') or config_id)}[/bold] [dim]{config_id} · {integration}[/dim]")
1024
+ published = config.get("publishedRevisionId")
1025
+ _say_kv(
1026
+ "live now",
1027
+ f"revision [bold]{published}[/bold]"
1028
+ if published
1029
+ else "[yellow]nothing — never published, so no tool call resolves it yet[/yellow]",
1030
+ )
1031
+ if config.get("egressCountry"):
1032
+ _say_kv("egress", f"{escape(str(config['egressCountry']))} [dim](draft)[/dim]")
1033
+ health = config.get("credentialHealth")
1034
+ if isinstance(health, str) and health and health.upper() != "HEALTHY":
1035
+ detail = config.get("credentialHealthDetail")
1036
+ say_warn(f"Credential health: {escape(health)}", hint=escape(str(detail)) if detail else None)
1037
+
1038
+
1039
+ def _hint_for_config_error(error: FruxonError, integration: str) -> str | None:
1040
+ from fruxon.exceptions import NotFoundError
1041
+ from fruxon.ui import hint_for_forbidden
1042
+
1043
+ if isinstance(error, NotFoundError):
1044
+ return (
1045
+ f"No such config under [bold]{integration}[/bold] — list them with "
1046
+ f"[bold]fruxon integrations configs list {integration}[/bold]."
1047
+ )
1048
+ # Before _hint_for_error, whose generic 403 line would hide the missing scope.
1049
+ forbidden_hint = hint_for_forbidden(error)
1050
+ if forbidden_hint is not None:
1051
+ return forbidden_hint
1052
+ return _hint_for_error(error)
1053
+
1054
+
784
1055
  def _build_configs_table(configs: list[dict]):
785
1056
  from rich.table import Table
786
1057
 
@@ -217,6 +217,21 @@ _MAX_PAGE_SIZE = 100
217
217
  # :meth:`FruxonClient.list_participants`.
218
218
  _VALID_PARTICIPANT_TIER = frozenset({"CURATED", "AMBIENT"})
219
219
 
220
+ # What :meth:`FruxonClient.publish_integration_config` keeps of the revision
221
+ # the server returns. That response is the snapshot itself, credentials
222
+ # decrypted, so this lists what may leave rather than what may not: a field
223
+ # the server adds later stays out until someone decides it is safe.
224
+ _PUBLISHED_INTEGRATION_REVISION_FIELDS = (
225
+ "id",
226
+ "configId",
227
+ "version",
228
+ "note",
229
+ "sandboxMode",
230
+ "egressCountry",
231
+ "createdBy",
232
+ "createdAt",
233
+ )
234
+
220
235
  _STATUS_EXCEPTIONS: dict[int, type[FruxonAPIError]] = {
221
236
  400: ValidationError,
222
237
  401: AuthenticationError,
@@ -2565,6 +2580,60 @@ class FruxonClient:
2565
2580
  )
2566
2581
  return raw
2567
2582
 
2583
+ def update_integration_config(self, integration: str, config_id: str, body: dict[str, object]) -> dict:
2584
+ """Patch a tenant-level integration config's draft.
2585
+
2586
+ Hits ``PATCH /v1/tenants/{tenant}/integrations/{integration}/configs/{configId}``
2587
+ with an ``UpdateTenantIntegrationConfigRequest`` (scope
2588
+ ``integrations:write``). Only the draft changes: tool calls keep
2589
+ resolving the published revision until :meth:`publish_integration_config`.
2590
+
2591
+ Top-level fields left out of ``body`` keep their saved values.
2592
+ ``parameters``, ``auth`` and ``authMap`` replace the saved value whole
2593
+ when present; send a secret's masked value from
2594
+ :meth:`get_integration_config` back unchanged to keep it.
2595
+ ``egressCountry: ""`` clears the egress pin.
2596
+
2597
+ Returns the updated config in the shape :meth:`get_integration_config`
2598
+ returns, secrets masked by the server.
2599
+ """
2600
+ url = self._integration_url(integration, f"/configs/{config_id}")
2601
+ raw = self._put_or_patch(url, body, method="PATCH")
2602
+ if not isinstance(raw, dict):
2603
+ raise FruxonAPIError(
2604
+ status=200,
2605
+ title="Unexpected response",
2606
+ detail="Expected an integration config object.",
2607
+ )
2608
+ return raw
2609
+
2610
+ def publish_integration_config(self, integration: str, config_id: str, *, note: str | None = None) -> dict:
2611
+ """Publish a config's current draft as a new revision.
2612
+
2613
+ Hits ``POST /v1/tenants/{tenant}/integrations/{integration}/configs/{configId}:publish``
2614
+ (scope ``integrations:write``). Snapshots the draft into a new,
2615
+ immutable revision and points the config's ``publishedRevisionId`` at
2616
+ it; tool calls resolving this config use it from their next call.
2617
+
2618
+ The server echoes the whole snapshot back, credentials included and
2619
+ unmasked. This returns only the revision's identity and non-secret
2620
+ fields: ``id``, ``configId``, ``version``, ``note``, ``sandboxMode``,
2621
+ ``egressCountry``, ``createdBy`` and ``createdAt``, each when the
2622
+ server sent it.
2623
+ """
2624
+ url = self._integration_url(integration, f"/configs/{config_id}:publish")
2625
+ body: dict[str, object] = {}
2626
+ if note:
2627
+ body["note"] = note
2628
+ result = self._post_json(url, body)
2629
+ if not isinstance(result, dict):
2630
+ raise FruxonAPIError(
2631
+ status=200,
2632
+ title="Unexpected response",
2633
+ detail="Expected an integration config revision object.",
2634
+ )
2635
+ return {key: result[key] for key in _PUBLISHED_INTEGRATION_REVISION_FIELDS if key in result}
2636
+
2568
2637
  # -------------------------------------------------------------------- mcp
2569
2638
 
2570
2639
  def get_mcp_config(self, integration: str) -> dict:
@@ -103,6 +103,7 @@ the bypass flag.
103
103
  | `triggers fire` (no --yes) | Pass `--yes` — it runs the bound agent(s) now (cost + side effects). |
104
104
  | `triggers ledger walk` (no --yes) | Pass `--yes` — every step is a paid model run on the agent's test budget (up to `--max-steps`, default 10). It writes nothing; read `stopped.code` in the JSON, since it exits 0 whenever it ran. |
105
105
  | `triggers delete` / `unbind` (no --yes) | Pass `--yes` — removes the trigger / its agent wiring. |
106
+ | `integrations configs publish` (no --yes) | Pass `--yes` — **but only on the human's say-so.** Every tool call that resolves the config switches to the new revision immediately. |
106
107
  | `participants delete` (no --yes) | Pass `--yes` — removes the participant from the network. |
107
108
  | `capabilities delete` (no --yes) | Pass `--yes` — removes the capability from the routing vocabulary. |
108
109
  | `participants unbind` (no --yes) | Pass `--yes` — removes the participant from the agent's consult roster. |
@@ -64,6 +64,7 @@ fruxon agents sandbox test <file|dir> # run scenario e2e tests of an agent (exi
64
64
  fruxon integrations list / create
65
65
  fruxon integrations authorize <integration> # mint an OAuth connect link for a human to click (auto-detects the method)
66
66
  fruxon integrations triggers <integration> # event types (+ payload paths) an integration can fire an agent on
67
+ fruxon integrations configs list / get / update / publish <integration> # saved credentials; update edits the draft, publish makes it live
67
68
  fruxon tools list <integration> / create
68
69
  fruxon assets create/list/get/wait/delete # the indexed RAG assets an agent's assetConfig attaches
69
70
  fruxon assets search/documents/chunks <a> # what the INDEX holds — distinct from the authored corpus below
@@ -27,6 +27,14 @@ You are now operating as a Fruxon integrations specialist.
27
27
  needs. `fruxon integrations configs get <integration> <config>`
28
28
  prints one config's full payload (auth shape, `publishedRevisionId`)
29
29
  — use it to confirm a config is published before pointing a slot at it.
30
+ - `fruxon integrations configs update <integration> <config> --file <patch.json>`
31
+ changes a config's **draft**: the file holds only the fields to change
32
+ (e.g. `{"egressCountry": ""}` clears an egress pin). Tool calls keep
33
+ resolving the **published** revision, so follow with
34
+ `fruxon integrations configs publish <integration> <config> --yes`, which
35
+ every agent using the config picks up on its next call — ask the human
36
+ first. Don't collect secrets in chat to put in a patch: credentials are
37
+ entered in the dashboard.
30
38
  - `fruxon integrations get <id>` — catalog metadata for one
31
39
  integration (description, tags). (It does not report auth status —
32
40
  use `configs list` for that.)
@@ -194,6 +194,9 @@ def test_the_doc_documents_something():
194
194
  # expanded — the blind spot that hid `agents test`.
195
195
  assert "knowledge-bases source preview" in documented
196
196
  assert "agents executions trace" in documented
197
+ # Expanded from the meet guide's `list / get / update / publish`.
198
+ assert "integrations configs update" in documented
199
+ assert "integrations configs publish" in documented
197
200
  # The three removed commands must not come back into the page.
198
201
  assert not any(d.split()[0] in {"run", "chat"} for d in documented), documented
199
202
  assert not any(d == "agents test" or d.startswith("agents test ") for d in documented), documented