failproofai 1.0.12-beta.2 → 1.0.12

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 (196) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +3 -3
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  51. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  54. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  57. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00l2u6j86cb8y._.js → [root-of-the-server]__167mipqmjprq5._.js} +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{_1fw0_nzkae5ao._.js → _1d98fyfrcaqhy._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1fv03uigni8s5.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  87. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  88. package/.next/standalone/.next/server/pages/404.html +1 -1
  89. package/.next/standalone/.next/server/pages/500.html +1 -1
  90. package/.next/standalone/.next/server/preview-props.json +3 -3
  91. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  93. package/.next/standalone/.next/static/chunks/{1c_jf6g5wnslk.js → 0el9n3h9_zp5m.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
  95. package/.next/standalone/.next/static/chunks/{2o6d37j425uci.js → 0umzct31duk5g.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{42xptz88mtq3x.js → 0wobqgosirs8p.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
  98. package/.next/standalone/.next/static/chunks/{0ay__-d2h1xb5.js → 1kduj74-715jh.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/{40oworn_-x12p.js → 2701amhrbsbot.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/{0tbm3vjm62h-2.js → 27cavoc8paelg.js} +1 -1
  101. package/.next/standalone/.next/static/chunks/{1my3-5p47t7r-.js → 2ve23a7keupjw.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/{21hckpmsc4g70.js → 3bv6xaoitjpnh.js} +2 -2
  103. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +19 -0
  104. package/.next/standalone/fp-cloud-cli/README.md +120 -0
  105. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
  106. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +198 -7
  107. package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
  108. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
  109. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
  110. package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
  111. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
  112. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
  113. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
  114. package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
  115. package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
  116. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
  117. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
  118. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
  119. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
  120. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
  121. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
  122. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
  123. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
  124. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
  125. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
  126. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
  127. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
  128. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
  129. package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
  130. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +187 -81
  131. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
  132. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
  133. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
  134. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +168 -16
  135. package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
  136. package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
  137. package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
  138. package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
  139. package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
  140. package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
  141. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
  142. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
  143. package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
  144. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
  145. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
  146. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
  147. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
  148. package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
  149. package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
  150. package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
  151. package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
  152. package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
  153. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
  154. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  155. package/.next/standalone/package.json +9 -9
  156. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
  157. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
  158. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
  159. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
  160. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
  161. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
  162. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
  163. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
  164. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
  165. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
  166. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
  167. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
  168. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
  169. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
  170. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
  171. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
  172. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
  173. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
  174. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
  175. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
  176. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
  177. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
  178. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
  179. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
  180. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
  181. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
  182. package/.next/standalone/server.js +1 -1
  183. package/dist/cli.mjs +85 -37
  184. package/dist/worker.mjs +73 -28
  185. package/hermes-plugin/plugin.yaml +1 -1
  186. package/package.json +9 -9
  187. package/src/hooks/agent-skill.ts +3 -0
  188. package/src/hooks/fp-home.ts +1 -1
  189. package/src/hooks/handler.ts +22 -3
  190. package/src/hooks/semantic/intent.ts +127 -24
  191. package/src/hooks/semantic/jev-review.ts +21 -2
  192. package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +0 -1
  193. package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +0 -1
  194. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
  195. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
  196. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
@@ -0,0 +1,116 @@
1
+ import json
2
+
3
+ import pytest
4
+ import respx
5
+
6
+ from fp_cli.app import app
7
+
8
+ BASE = "http://dash.test"
9
+ DASHBOARD = {"name": "Quality", "description": "Keep", "definition": {"filters": {"env": "prod"}}}
10
+ TILE = {"id": "t1", "query_id": "q1", "title": "Before", "chart_type": "table",
11
+ "chart_config": {}, "param_bindings": {}, "pos_x": 0, "pos_y": 0, "pos_w": 6, "pos_h": 4}
12
+ RESULT = {"columns": [{"name": "agent", "type": "String"}, {"name": "n", "type": "Int64"}],
13
+ "rows": [["agent-001", 42]], "truncated": False, "elapsed_ms": 1}
14
+ CASES = [
15
+ (["dashboards", "list"], "GET", "/dashboards", {"dashboards": []}),
16
+ (["dashboards", "show", "d1"], "GET", "/dashboards/d1", {"dashboard": DASHBOARD, "tiles": []}),
17
+ (["dashboards", "create", "--name", "Quality"], "POST", "/dashboards", {"id": "d1"}),
18
+ (["dashboards", "update", "d1", "--name", "New"], "PUT", "/dashboards/d1", {"id": "d1"}),
19
+ (["dashboards", "delete", "d1"], "DELETE", "/dashboards/d1", {"deleted": True}),
20
+ (["dashboards", "tiles", "d1", "list"], "GET", "/dashboards/d1/tiles", {"tiles": [TILE]}),
21
+ (["dashboards", "tiles", "d1", "add", "--query", "q1", "--chart", "bar"], "POST", "/dashboards/d1/tiles", {"id": "t1"}),
22
+ (["dashboards", "tiles", "d1", "update", "t1", "--title", "After"], "PUT", "/dashboards/d1/tiles/t1", {"id": "t1"}),
23
+ (["dashboards", "tiles", "d1", "remove", "t1"], "DELETE", "/dashboards/d1/tiles/t1", {"deleted": True}),
24
+ (["dashboards", "tiles", "d1", "layout", "--file", "{layout}"], "PUT", "/dashboards/d1/tiles/layout", {"updated": 1}),
25
+ (["sessions", "show", "s1"], "GET", "/events/summary", {"events": [], "next_cursor": None}),
26
+ (["sessions", "export", "s1"], "GET", "/sessions/s1/export", {"session_id": "s1", "events": []}),
27
+ (["sessions", "history", "s1"], "GET", "/sessions/s1/evaluation-history", []),
28
+ (["events", "latency"], "GET", "/events/latency_aggregate", {"p95": 42}),
29
+ (["sentiment", "summary"], "GET", "/events/sentiment", {"positive": 1}),
30
+ (["sentiment", "messages"], "GET", "/events/sentiment/messages", []),
31
+ (["sentiment", "sessions"], "GET", "/events/sentiment/sessions", []),
32
+ (["sentiment", "session", "s1"], "GET", "/events/sentiment/sessions/s1", {}),
33
+ (["query", "export", "q1", "--format", "json"], "POST", "/queries/run", RESULT),
34
+ (["query", "export", "--sql", "SELECT 1", "--format", "csv"], "POST", "/queries/run", RESULT),
35
+ ]
36
+
37
+
38
+ @pytest.fixture
39
+ def files(tmp_path):
40
+ path = tmp_path / "layout.json"
41
+ path.write_text(json.dumps({"tiles": [{k: v for k, v in TILE.items() if k in ("id", "pos_x", "pos_y", "pos_w", "pos_h")}]}))
42
+ return {"layout": str(path)}
43
+
44
+
45
+ @pytest.mark.parametrize("key_mode", [False, True])
46
+ @pytest.mark.parametrize("argv,method,path,response", CASES, ids=lambda v: " ".join(v) if isinstance(v, list) else str(v))
47
+ def test_observability_commands(logged_in, runner, files, key_mode, argv, method, path, response):
48
+ prefix = "/v1" if key_mode else "/api"
49
+ with respx.mock(assert_all_called=False) as mock:
50
+ mock.get(f"{BASE}{prefix}/dashboards/d1").respond(200, json={"dashboard": DASHBOARD, "tiles": []})
51
+ mock.get(f"{BASE}{prefix}/dashboards/d1/tiles").respond(200, json={"tiles": [TILE]})
52
+ mock.get(f"{BASE}{prefix}/sessions").respond(200, json={"sessions": [{"session_id": "s1"}]})
53
+ route = mock.request(method, f"{BASE}{prefix}{path}").respond(200, json=response)
54
+ result = runner.invoke(app, ["--json", *(["--api-key", "key"] if key_mode else []), *[v.format(**files) for v in argv]])
55
+ assert result.exit_code == 0, result.output
56
+ assert route.called
57
+ json.loads(result.stdout)
58
+ if argv[:2] == ["dashboards", "create"]:
59
+ assert json.loads(route.calls[0].request.content) == {"name": "Quality", "description": "", "definition": {}}
60
+ if argv[:2] == ["dashboards", "update"]:
61
+ assert json.loads(route.calls[0].request.content) == {**DASHBOARD, "name": "New"}
62
+ if argv[:4] == ["dashboards", "tiles", "d1", "update"]:
63
+ assert json.loads(route.calls[0].request.content) == {k: ("After" if k == "title" else v) for k, v in TILE.items() if k != "id"}
64
+
65
+
66
+ @pytest.mark.parametrize("argv,method,path,response", CASES)
67
+ @pytest.mark.parametrize("key_mode", [False, True])
68
+ def test_observability_404(logged_in, runner, files, key_mode, argv, method, path, response):
69
+ with respx.mock as mock:
70
+ mock.route().respond(404, json={"error": "unknown route"})
71
+ result = runner.invoke(app, ["--json", *(["--api-key", "key"] if key_mode else []), *[v.format(**files) for v in argv]])
72
+ assert result.exit_code == 6, result.output
73
+ assert "not available on this FailproofAI Cloud yet" in result.stdout
74
+
75
+
76
+ def test_query_compose(logged_in, runner):
77
+ stream = 'data: {"type":"done","sql":"SELECT 1"}\n\n'
78
+ with respx.mock as mock:
79
+ draft = mock.post(f"{BASE}/api/agent/compose-sql").respond(200, text=stream, headers={"content-type": "text/event-stream"})
80
+ run = mock.post(f"{BASE}/api/queries/run").respond(200, json=RESULT)
81
+ result = runner.invoke(app, ["--json", "query", "compose", "Count events", "--run"])
82
+ assert result.exit_code == 0, result.output
83
+ assert json.loads(draft.calls[0].request.content) == {"intent": "Count events"}
84
+ assert json.loads(run.calls[0].request.content) == {"sql": "SELECT 1", "params": []}
85
+
86
+
87
+ def test_query_compose_key_denial(logged_in, runner):
88
+ with respx.mock as mock:
89
+ result = runner.invoke(app, ["--json", "--api-key", "key", "query", "compose", "Count events"])
90
+ assert not mock.calls
91
+ assert result.exit_code == 2
92
+ assert "fp login" in result.stdout
93
+
94
+
95
+ def test_exports_write_files(logged_in, runner, tmp_path):
96
+ path = tmp_path / "data.csv"
97
+ with respx.mock as mock:
98
+ mock.post(f"{BASE}/api/queries/run").respond(200, json=RESULT)
99
+ result = runner.invoke(app, ["--json", "query", "export", "--sql", "SELECT 1", "--out", str(path)])
100
+ assert result.exit_code == 0
101
+ assert path.read_text().splitlines() == ["agent,n", "agent-001,42"]
102
+
103
+
104
+ @pytest.mark.parametrize("argv", [
105
+ ["dashboards", "create", "--name", ""],
106
+ ["dashboards", "update", "d1"],
107
+ ["dashboards", "tiles", "d1", "add", "--query", "q1", "--chart", "invalid"],
108
+ ["sentiment", "messages", "--limit", "0"],
109
+ ["query", "export", "--sql", "SELECT 1", "--format", "xml"],
110
+ ["query", "export", "q1", "--sql", "SELECT 1"],
111
+ ])
112
+ def test_validation(logged_in, runner, argv):
113
+ with respx.mock as mock:
114
+ result = runner.invoke(app, ["--json", *argv])
115
+ assert not mock.calls
116
+ assert result.exit_code == 2, result.output
@@ -0,0 +1,141 @@
1
+ import pytest
2
+ import respx
3
+
4
+ from fp_cli import client
5
+ from fp_cli.errors import NotFoundError
6
+
7
+
8
+ @pytest.mark.parametrize("family", [
9
+ "evaluator-definitions", "evaluator-instances", "evaluator-schema-profile",
10
+ "evaluation-jobs", "dashboards", "otel", "offers", "team-trial",
11
+ ])
12
+ def test_added_v1_families(family):
13
+ assert client._v1_path(f"/api/{family}") == f"/v1/{family}"
14
+
15
+
16
+ @pytest.mark.parametrize("method", ["GET", "POST", "PATCH", "DELETE"])
17
+ @pytest.mark.parametrize("key_mode", [False, True])
18
+ def test_cloud_request_degrades(method, key_mode):
19
+ ctx = client.ClientContext(
20
+ "http://dash.test", token=None if key_mode else "tok",
21
+ api_key="key" if key_mode else None,
22
+ auth_mode=client.AuthMode.API_KEY if key_mode else client.AuthMode.SESSION,
23
+ )
24
+ prefix = "v1" if key_mode else "api"
25
+ with respx.mock:
26
+ route = respx.request(method, f"http://dash.test/{prefix}/dashboards").respond(404, json={})
27
+ with pytest.raises(NotFoundError, match="not available on this FailproofAI Cloud yet") as exc:
28
+ client.cloud_request(ctx, method, "/api/dashboards")
29
+ assert exc.value.exit_code == 6
30
+ headers = route.calls[0].request.headers
31
+ assert ("authorization" in headers) is key_mode
32
+ assert ("cookie" in headers) is not key_mode
33
+
34
+
35
+ def test_new_capability_html_404_degrades():
36
+ ctx = client.ClientContext("http://dash.test", api_key="key", auth_mode=client.AuthMode.API_KEY)
37
+ with respx.mock as mock:
38
+ mock.get("http://dash.test/v1/dashboards").respond(404, text="<html>old Cloud</html>", headers={"content-type": "text/html"})
39
+ with pytest.raises(NotFoundError, match="not available on this FailproofAI Cloud yet") as exc:
40
+ client.cloud_request(ctx, "GET", "/api/dashboards")
41
+ assert exc.value.exit_code == 6
42
+
43
+
44
+ @pytest.mark.parametrize("key_mode", [False, True])
45
+ @pytest.mark.parametrize("method,path,body,params", [
46
+ ("PATCH", "/api/evaluator-definitions/stale-id", {"enabled": False}, None),
47
+ ("GET", "/api/sessions/missing/export", None, None),
48
+ ("POST", "/api/sessions/missing/re-evaluate", None, None),
49
+ ("GET", "/api/dashboards/missing/tiles", None, None),
50
+ ("DELETE", "/api/dashboards/board/tiles/stale-tile", None, None),
51
+ ("GET", "/api/events/sentiment/sessions/missing", None, None),
52
+ ("GET", "/api/audits/contracts/stale-id", None, None),
53
+ ("POST", "/api/queries/run", {"query_id": "missing"}, None),
54
+ ("POST", "/api/agent/answer", {"callId": "missing", "decision": "reject"}, None),
55
+ ("DELETE", "/api/settings/model-context-windows", None, {"model": "missing"}),
56
+ ])
57
+ def test_record_404_is_not_route_unavailable(key_mode, method, path, body, params):
58
+ ctx = client.ClientContext(
59
+ "http://dash.test", token=None if key_mode else "tok",
60
+ api_key="key" if key_mode else None,
61
+ auth_mode=client.AuthMode.API_KEY if key_mode else client.AuthMode.SESSION,
62
+ )
63
+ if key_mode and path.startswith("/api/agent/"):
64
+ pytest.skip("the agent has no key route; tested via session-only command guards")
65
+ wire_path = client._v1_path(path) if key_mode else path
66
+ with respx.mock as mock:
67
+ mock.request(method, f"http://dash.test{wire_path}").respond(404, json={})
68
+ with pytest.raises(NotFoundError) as exc:
69
+ client.cloud_request(ctx, method, path, body=body, params=params)
70
+ assert exc.value.exit_code == 6
71
+ assert "Record not found" in exc.value.message
72
+ assert "not available on this FailproofAI Cloud yet" not in exc.value.message
73
+
74
+
75
+ @pytest.mark.parametrize("response,route_unknown", [
76
+ ({"code": "route_not_found"}, True),
77
+ ({"error": {"code": "route_not_found"}}, True),
78
+ ({"error": "unknown endpoint"}, True),
79
+ ({"message": "Cannot PATCH /evaluator-definitions/stale-id"}, True),
80
+ ({"error": "route does not exist"}, True),
81
+ ({"error": "old Cloud route absent"}, True),
82
+ ({"error": "evaluator definition not found"}, False),
83
+ ({"error": "session not found"}, False),
84
+ ({"error": "query not found"}, False),
85
+ ({"error": "not available in demo"}, False),
86
+ ])
87
+ def test_404_response_overrides_request_shape(response, route_unknown):
88
+ ctx = client.ClientContext("http://dash.test", token="tok")
89
+ with respx.mock as mock:
90
+ mock.patch("http://dash.test/api/evaluator-definitions/stale-id").respond(404, json=response)
91
+ with pytest.raises(NotFoundError) as exc:
92
+ client.cloud_request(ctx, "PATCH", "/api/evaluator-definitions/stale-id", body={"enabled": False})
93
+ assert exc.value.exit_code == 6
94
+ if route_unknown:
95
+ assert exc.value.message == "not available on this FailproofAI Cloud yet"
96
+ else:
97
+ assert exc.value.message == response.get("error", response.get("message"))
98
+
99
+
100
+ @pytest.mark.parametrize("body", [{"code": "record_not_found"}, {"error": "resource_not_found"}])
101
+ def test_record_error_code_on_collection_is_not_unavailable(body):
102
+ ctx = client.ClientContext("http://dash.test", token="tok")
103
+ with respx.mock as mock:
104
+ mock.post("http://dash.test/api/queries/run").respond(404, json=body)
105
+ with pytest.raises(NotFoundError, match="Record not found") as exc:
106
+ client.cloud_request(ctx, "POST", "/api/queries/run", body={"sql": "SELECT 1"})
107
+ assert "not available" not in exc.value.message
108
+
109
+
110
+ def test_collection_with_missing_saved_query_preserves_server_error():
111
+ ctx = client.ClientContext("http://dash.test", token="tok")
112
+ with respx.mock as mock:
113
+ mock.post("http://dash.test/api/queries/run").respond(404, json={"error": "saved query not found"})
114
+ with pytest.raises(NotFoundError, match="saved query not found"):
115
+ client.cloud_request(ctx, "POST", "/api/queries/run", body={"query_id": "stale"})
116
+
117
+
118
+ def test_stream_404_distinguishes_route_and_record():
119
+ ctx = client.ClientContext("http://dash.test", token="tok")
120
+ for error, expected in [
121
+ ({"code": "unknown_route"}, "not available on this FailproofAI Cloud yet"),
122
+ ({"error": "conversation not found"}, "conversation not found"),
123
+ ]:
124
+ with respx.mock as mock:
125
+ mock.post("http://dash.test/api/agent/chat").respond(404, json=error)
126
+ with pytest.raises(NotFoundError, match=expected):
127
+ list(client.cloud_stream(ctx, "/api/agent/chat", {"conversationId": "missing"}))
128
+
129
+
130
+ @pytest.mark.parametrize("path", [
131
+ "/api/agent/conversations/stale",
132
+ "/api/offers/stale",
133
+ "/api/integrations/slack/stale",
134
+ ])
135
+ def test_named_endpoint_families_do_not_hide_record_404s(path):
136
+ ctx = client.ClientContext("http://dash.test", token="tok")
137
+ with respx.mock as mock:
138
+ mock.get(f"http://dash.test{path}").respond(404, json={})
139
+ with pytest.raises(NotFoundError, match="Record not found") as exc:
140
+ client.cloud_request(ctx, "GET", path)
141
+ assert "not available" not in exc.value.message
@@ -522,7 +522,7 @@ def test_sessions_table(logged_in, runner):
522
522
  )
523
523
  result = runner.invoke(app, ["sessions"])
524
524
  assert result.exit_code == 0, result.output
525
- assert "sessions" in result.stdout # the boxed panel title
525
+ assert "SESSIONS" in result.stdout
526
526
  assert "done" in result.stdout # status flattened up from latest_evaluation
527
527
 
528
528
 
@@ -679,7 +679,7 @@ def test_help_command(home, runner):
679
679
  result = runner.invoke(app, ["help"])
680
680
  assert result.exit_code == 0
681
681
  out = result.stdout
682
- assert "Commands" in out # the grouped top-level panel
682
+ assert "COMMANDS" in out
683
683
  # the four purpose groups, in order
684
684
  for g in ("ESSENTIALS", "OBSERVE", "MANAGE", "TOOLS"):
685
685
  assert g in out
@@ -744,7 +744,7 @@ def test_fields_unknown_is_usage_error(logged_in, runner):
744
744
  def test_dash_h_alias_shows_help(home, runner):
745
745
  result = runner.invoke(app, ["-h"])
746
746
  assert result.exit_code == 0
747
- assert "Commands" in result.stdout and "sessions" in result.stdout
747
+ assert "COMMANDS" in result.stdout and "sessions" in result.stdout
748
748
  assert "ESSENTIALS" in result.stdout # the grouped top-level help, via the rich_format_help override
749
749
 
750
750
 
@@ -171,7 +171,7 @@ def test_agent_health_renders_configured_card(logged_in, runner):
171
171
  result = runner.invoke(app, ["agent", "health"])
172
172
  assert result.exit_code == 0, result.output
173
173
  out = result.stdout
174
- assert "assistant" in out and "configured" in out and "claude-x" in out
174
+ assert "ASSISTANT" in out and "configured" in out and "claude-x" in out
175
175
 
176
176
 
177
177
  @respx.mock
@@ -192,7 +192,7 @@ def test_agent_models_renders_default_marker(logged_in, runner):
192
192
  result = runner.invoke(app, ["agent", "models"])
193
193
  assert result.exit_code == 0, result.output
194
194
  out = result.stdout
195
- assert "models" in out and "m-default" in out and "m-fast" in out and "default" in out
195
+ assert "MODELS" in out and "m-default" in out and "m-fast" in out and "default" in out
196
196
 
197
197
 
198
198
  @respx.mock
@@ -213,7 +213,7 @@ def test_agent_chats_renders_table(logged_in, runner):
213
213
  )
214
214
  result = runner.invoke(app, ["agent", "chats"])
215
215
  assert result.exit_code == 0, result.output
216
- assert "chats" in result.stdout and "perf review" in result.stdout
216
+ assert "CHATS" in result.stdout and "perf review" in result.stdout
217
217
  assert "chat-id" in result.stdout and "c1" in result.stdout # full chat-id column by default
218
218
  assert "open one with" not in (result.stderr or "") # footer removed
219
219