insightfactory-cli 1.0.3.dev19__tar.gz → 1.0.3.dev21__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 (68) hide show
  1. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/PKG-INFO +7 -1
  2. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/README.md +6 -0
  3. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/pyproject.toml +1 -1
  4. insightfactory_cli-1.0.3.dev21/src/if_cli/assets/__init__.py +1 -0
  5. insightfactory_cli-1.0.3.dev21/src/if_cli/assets/insightfactoryai-logo.svg +55 -0
  6. insightfactory_cli-1.0.3.dev21/src/if_cli/callback_page.py +215 -0
  7. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/mcp.py +2 -1
  8. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/oauth.py +5 -4
  9. insightfactory_cli-1.0.3.dev21/src/if_cli/router/log.py +13 -0
  10. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/router/server.py +1 -5
  11. insightfactory_cli-1.0.3.dev21/src/if_cli/router/upstream.py +272 -0
  12. insightfactory_cli-1.0.3.dev21/tests/test_router_upstream.py +370 -0
  13. insightfactory_cli-1.0.3.dev19/src/if_cli/router/upstream.py +0 -107
  14. insightfactory_cli-1.0.3.dev19/tests/test_router_upstream.py +0 -80
  15. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/.github/workflows/ci.yml +0 -0
  16. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/.github/workflows/claude.yml +0 -0
  17. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/.github/workflows/release.yml +0 -0
  18. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/.gitignore +0 -0
  19. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/.python-version +0 -0
  20. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/AGENTS.md +0 -0
  21. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/CLAUDE.md +0 -0
  22. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/LICENSE +0 -0
  23. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/__init__.py +0 -0
  24. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/__main__.py +0 -0
  25. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/cache.py +0 -0
  26. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/cli.py +0 -0
  27. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/colour.py +0 -0
  28. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/__init__.py +0 -0
  29. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/api.py +0 -0
  30. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/config.py +0 -0
  31. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/login.py +0 -0
  32. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/logout.py +0 -0
  33. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/profiles.py +0 -0
  34. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/set_token.py +0 -0
  35. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/commands/token.py +0 -0
  36. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/config.py +0 -0
  37. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/constants.py +0 -0
  38. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/http.py +0 -0
  39. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/main.py +0 -0
  40. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/router/__init__.py +0 -0
  41. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/router/catalog.py +0 -0
  42. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/router/policy.py +0 -0
  43. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/src/if_cli/runtime.py +0 -0
  44. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/__init__.py +0 -0
  45. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/cache_writer.py +0 -0
  46. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/conftest.py +0 -0
  47. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/helpers.py +0 -0
  48. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/servers.py +0 -0
  49. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_api.py +0 -0
  50. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_api_command.py +0 -0
  51. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_cache.py +0 -0
  52. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_cli.py +0 -0
  53. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_config.py +0 -0
  54. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_config_command.py +0 -0
  55. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_login.py +0 -0
  56. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_mcp_command.py +0 -0
  57. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_oauth.py +0 -0
  58. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_oauth_flow.py +0 -0
  59. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_oauth_force_refresh.py +0 -0
  60. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_profiles.py +0 -0
  61. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_programmatic_api.py +0 -0
  62. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_router_catalog.py +0 -0
  63. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_router_policy.py +0 -0
  64. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_router_server.py +0 -0
  65. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_runtime.py +0 -0
  66. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_set_token.py +0 -0
  67. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/tests/test_token.py +0 -0
  68. {insightfactory_cli-1.0.3.dev19 → insightfactory_cli-1.0.3.dev21}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: insightfactory-cli
3
- Version: 1.0.3.dev19
3
+ Version: 1.0.3.dev21
4
4
  Summary: Profile-based authentication CLI for the InsightFactory Interfaces API
5
5
  Project-URL: Homepage, https://insightfactory.ai
6
6
  Author-email: "insightfactory.ai Support" <support@insightfactory.ai>
@@ -304,6 +304,12 @@ Passing dev-only writes to production means changing an argument value, not
304
304
  connecting to a different server, so keep `--writable` scoped to the
305
305
  environments meant to be written by hand.
306
306
 
307
+ A tool the factory will not run until someone finishes a step in a browser, such
308
+ as linking a personal Databricks identity, comes back as a failed call naming the
309
+ page to visit rather than as a prompt. The router never relays the factory's
310
+ request for input to the model, and it names a page only on that environment's
311
+ own origin, logging a `WARN` line on stderr when it drops one that is not.
312
+
307
313
  ## Development
308
314
 
309
315
  ```bash
@@ -280,6 +280,12 @@ Passing dev-only writes to production means changing an argument value, not
280
280
  connecting to a different server, so keep `--writable` scoped to the
281
281
  environments meant to be written by hand.
282
282
 
283
+ A tool the factory will not run until someone finishes a step in a browser, such
284
+ as linking a personal Databricks identity, comes back as a failed call naming the
285
+ page to visit rather than as a prompt. The router never relays the factory's
286
+ request for input to the model, and it names a page only on that environment's
287
+ own origin, logging a `WARN` line on stderr when it drops one that is not.
288
+
283
289
  ## Development
284
290
 
285
291
  ```bash
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "insightfactory-cli"
3
- version = "1.0.3.dev19"
3
+ version = "1.0.3.dev21"
4
4
  description = "Profile-based authentication CLI for the InsightFactory Interfaces API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ """Static files shipped with the CLI."""
@@ -0,0 +1,55 @@
1
+ <svg class="logo" role="img" aria-label="insightfactory.ai" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 286.67 62.53">
2
+ <defs>
3
+ <style>
4
+ .iflogo-1 {
5
+ fill: #fab314;
6
+ }
7
+ .iflogo-2 {
8
+ fill: #f8e86c;
9
+ }
10
+ .iflogo-3 {
11
+ fill: #fad121;
12
+ }
13
+ .iflogo-4 {
14
+ fill: #1f252e;
15
+ }
16
+ </style>
17
+ </defs>
18
+ <g id="ARTWORK">
19
+ <g>
20
+ <g>
21
+ <polygon class="iflogo-4" points="32.49 37.52 32.5 37.53 32.49 37.52 32.49 37.52"/>
22
+ <polygon class="iflogo-4" points="21.66 18.76 0 31.27 21.66 43.78 21.66 18.76"/>
23
+ <g>
24
+ <polygon class="iflogo-4" points="43.32 31.27 43.32 43.78 54.15 37.52 43.32 31.27"/>
25
+ <polygon class="iflogo-4" points="43.32 43.78 43.32 56.28 54.15 62.53 64.98 56.28 43.32 43.78"/>
26
+ </g>
27
+ <polygon class="iflogo-2" points="21.66 6.26 32.49 12.51 43.32 6.26 32.48 0 21.66 6.26"/>
28
+ <polygon class="iflogo-3" points="32.49 12.51 32.49 12.51 21.66 6.26 21.66 18.76 21.66 18.76 21.66 43.78 32.49 50.03 32.49 37.52 32.49 12.51"/>
29
+ <g>
30
+ <polygon class="iflogo-1" points="32.49 12.51 32.5 25.02 43.32 18.76 43.32 6.26 32.49 12.51"/>
31
+ <polygon class="iflogo-1" points="43.32 56.28 43.32 43.78 43.32 31.27 32.49 37.52 32.49 50.03 43.32 56.28"/>
32
+ </g>
33
+ </g>
34
+ <g>
35
+ <path class="iflogo-4" d="m75.28,23.74c-.81,0-1.47-.27-1.99-.8-.52-.53-.77-1.17-.77-1.91s.26-1.41.77-1.93,1.18-.78,1.99-.78c.76,0,1.41.26,1.94.78.53.52.79,1.17.79,1.93s-.26,1.38-.79,1.91c-.53.53-1.17.8-1.94.8Zm-2.19,2.23h4.31v17.39h-4.31v-17.39Z"/>
36
+ <path class="iflogo-4" d="m79.46,25.97h3.91l.3,1.77c.54-.63,1.2-1.12,1.97-1.48.78-.36,1.61-.54,2.51-.54,1.33,0,2.46.29,3.39.87.93.58,1.65,1.42,2.16,2.52.51,1.1.76,2.46.76,4.09v10.16h-4.35v-9.57c0-1.34-.26-2.35-.78-3.03s-1.29-1.01-2.33-1.01-1.86.34-2.41,1.03c-.55.68-.83,1.7-.83,3.04v9.53h-4.31v-17.39Z"/>
37
+ <path class="iflogo-4" d="m100.08,37.69c.13.58.36,1.02.69,1.32.33.3.7.52,1.11.64.42.13.84.19,1.26.19.79,0,1.4-.16,1.85-.47.45-.31.67-.71.67-1.2,0-.42-.16-.75-.47-.99-.31-.24-.71-.43-1.2-.57-.48-.14-.99-.27-1.53-.38-.61-.16-1.25-.34-1.94-.54-.69-.2-1.33-.48-1.94-.85-.61-.37-1.11-.85-1.5-1.44-.39-.59-.59-1.34-.59-2.24,0-1.09.28-2.03.84-2.83.56-.8,1.35-1.43,2.38-1.9s2.21-.7,3.56-.7c1.59,0,2.94.35,4.04,1.04,1.1.7,1.9,1.69,2.39,2.99l-3.78,1.11c-.13-.32-.34-.59-.61-.8s-.59-.36-.96-.45c-.37-.09-.75-.14-1.13-.14-.65,0-1.19.13-1.62.4-.43.27-.64.63-.64,1.1,0,.28.08.5.24.68s.38.32.66.43c.28.12.61.22.98.31.37.09.78.2,1.23.31.7.19,1.4.4,2.11.64.71.24,1.36.55,1.96.92.59.37,1.07.87,1.43,1.5.36.63.56,1.42.61,2.37,0,1.02-.28,1.94-.84,2.77s-1.37,1.48-2.43,1.96c-1.06.49-2.34.73-3.84.73-1.75,0-3.24-.38-4.47-1.15s-2.03-1.96-2.41-3.58l3.88-1.18Z"/>
38
+ <path class="iflogo-4" d="m114.61,23.74c-.81,0-1.47-.27-1.99-.8-.52-.53-.77-1.17-.77-1.91s.26-1.41.77-1.93,1.18-.78,1.99-.78c.76,0,1.41.26,1.94.78.53.52.79,1.17.79,1.93s-.26,1.38-.79,1.91c-.53.53-1.17.8-1.94.8Zm-2.19,2.23h4.31v17.39h-4.31v-17.39Z"/>
39
+ <path class="iflogo-4" d="m131.9,25.97h3.91v14.82c0,1.97-.47,3.67-1.42,5.1-.94,1.43-2.31,2.52-4.09,3.29-1.79.76-3.93,1.15-6.42,1.15l-.67-3.76c2.72-.05,4.78-.54,6.18-1.5s2.11-2.32,2.11-4.1v-.07l.57-.21c-.54.81-1.28,1.5-2.22,2.07s-2.07.85-3.37.85c-1.48,0-2.79-.37-3.91-1.11-1.12-.74-2.01-1.78-2.65-3.11-.64-1.33-.96-2.91-.96-4.71s.32-3.39.96-4.73c.64-1.34,1.52-2.38,2.65-3.11,1.12-.73,2.43-1.1,3.91-1.1.76,0,1.48.11,2.14.33.66.22,1.25.51,1.75.87.5.36.93.76,1.26,1.2l.27-2.16Zm-8.32,8.7c0,1.46.37,2.64,1.09,3.53.73.89,1.7,1.34,2.92,1.34.7,0,1.34-.19,1.92-.57.58-.38,1.06-.94,1.43-1.69.37-.74.56-1.61.56-2.61s-.19-1.92-.56-2.64c-.37-.72-.85-1.27-1.45-1.65-.59-.38-1.23-.57-1.9-.57-1.21,0-2.19.45-2.92,1.34-.73.89-1.09,2.07-1.09,3.53Z"/>
40
+ <path class="iflogo-4" d="m138.5,18.32h4.31v9.36c.52-.58,1.16-1.05,1.94-1.41.78-.36,1.65-.54,2.61-.54,1.28,0,2.39.29,3.34.87.94.58,1.68,1.42,2.21,2.52.53,1.1.79,2.46.79,4.09v10.16h-4.55v-9.57c0-1.34-.26-2.35-.78-3.03s-1.29-1.01-2.33-1.01-1.86.37-2.41,1.1c-.55.73-.83,1.77-.83,3.11v9.39h-4.31v-25.04Z"/>
41
+ <path class="iflogo-4" d="m157.01,25.97v-4.87h4.35v4.87h3.74v3.93h-3.74v7.06c0,.9.19,1.54.57,1.91.38.37.86.57,1.43.59.57.02,1.15-.01,1.74-.1v3.83c-1.03.23-2.04.3-3.02.21-.98-.09-1.85-.37-2.61-.82-.76-.45-1.36-1.14-1.8-2.07-.44-.93-.66-2.11-.66-3.55v-7.06h-2.39v-3.93h2.39Z"/>
42
+ <path class="iflogo-4" d="m170.42,23.92c0-1.27.2-2.35.61-3.23s1.05-1.52,1.94-1.93c.89-.41,2.07-.55,3.56-.43v2.33c-1.35-.12-2.28.11-2.8.68s-.78,1.43-.78,2.59v2.05h3.71v2.43h-3.71v14.96h-2.53v-14.96h-2.36v-2.43h2.36v-2.05Z"/>
43
+ <path class="iflogo-4" d="m188.25,43.36l-.2-3.13c-.47,1.07-1.17,1.9-2.09,2.49-.92.59-2.09.89-3.5.89-1.21,0-2.25-.2-3.1-.59-.85-.39-1.51-.96-1.97-1.69-.46-.73-.69-1.59-.69-2.59,0-1.46.53-2.66,1.58-3.6,1.06-.94,2.58-1.51,4.58-1.72l5.19-.59v-1.39c0-.97-.35-1.76-1.04-2.35s-1.64-.89-2.83-.89-2.14.28-2.92.83c-.78.56-1.31,1.36-1.6,2.4l-2.29-.83c.45-1.53,1.28-2.72,2.48-3.58,1.2-.86,2.67-1.29,4.4-1.29,2,0,3.56.53,4.67,1.58,1.11,1.06,1.67,2.49,1.67,4.3v11.76h-2.33Zm-.2-8.31l-5.22.63c-1.12.14-1.98.46-2.56.96-.58.5-.88,1.15-.88,1.96s.29,1.46.86,1.95c.57.49,1.4.73,2.48.73,1.24,0,2.25-.25,3.03-.77.79-.51,1.37-1.2,1.74-2.07.37-.87.56-1.81.56-2.83v-.56Z"/>
44
+ <path class="iflogo-4" d="m201.02,43.61c-1.64,0-3.08-.37-4.31-1.11-1.24-.74-2.2-1.78-2.88-3.11-.69-1.33-1.03-2.88-1.03-4.64s.34-3.36,1.03-4.71,1.65-2.41,2.88-3.17c1.24-.75,2.67-1.13,4.31-1.13,1.73,0,3.25.41,4.55,1.23,1.3.82,2.25,1.97,2.83,3.43l-2.33.83c-.34-.93-.95-1.66-1.84-2.21-.89-.54-1.92-.82-3.08-.82s-2.14.27-2.98.8-1.49,1.29-1.96,2.26c-.46.97-.69,2.12-.69,3.44,0,1.95.51,3.51,1.52,4.68s2.36,1.76,4.04,1.76c1.15,0,2.17-.27,3.07-.8.9-.53,1.56-1.25,1.99-2.16l2.29.83c-.61,1.44-1.56,2.56-2.86,3.38-1.3.81-2.82,1.22-4.55,1.22Z"/>
45
+ <path class="iflogo-4" d="m212.07,25.97v-4.87h2.53v4.87h4.48v2.43h-4.48v9.36c0,1.07.22,1.84.66,2.33.44.49,1,.78,1.68.87.69.09,1.4.09,2.14,0v2.26c-.88.21-1.74.27-2.58.17-.84-.09-1.6-.35-2.26-.78-.66-.43-1.19-1.04-1.58-1.83-.39-.79-.59-1.8-.59-3.03v-9.36h-2.53v-2.43h2.53Z"/>
46
+ <path class="iflogo-4" d="m228.49,43.61c-1.6,0-3.02-.36-4.28-1.08-1.26-.72-2.25-1.74-2.97-3.08-.72-1.33-1.08-2.92-1.08-4.75s.37-3.46,1.1-4.8c.73-1.34,1.73-2.38,3-3.1,1.27-.72,2.7-1.08,4.3-1.08s3.03.36,4.3,1.08c1.27.72,2.27,1.75,3,3.08.73,1.33,1.1,2.92,1.1,4.75s-.37,3.46-1.11,4.8-1.75,2.38-3.03,3.1-2.72,1.08-4.31,1.08Zm-.03-2.47c.99,0,1.92-.23,2.8-.68.88-.45,1.59-1.16,2.14-2.12.55-.96.83-2.19.83-3.67s-.27-2.74-.81-3.69c-.54-.95-1.24-1.65-2.11-2.1s-1.79-.68-2.78-.68-1.91.23-2.76.68-1.54,1.16-2.07,2.12c-.53.96-.79,2.19-.79,3.67s.26,2.74.78,3.69c.52.95,1.2,1.65,2.04,2.1.84.45,1.76.68,2.75.68Z"/>
47
+ <path class="iflogo-4" d="m238.93,25.97h2.33l.13,2.54c.45-.63.99-1.16,1.63-1.6.64-.44,1.35-.75,2.12-.92.77-.17,1.6-.18,2.48-.02v2.61c-.94-.18-1.79-.18-2.54,0-.75.19-1.4.53-1.94,1.04-.54.51-.96,1.15-1.25,1.93-.29.78-.44,1.68-.44,2.7v9.11h-2.53v-17.39Z"/>
48
+ <path class="iflogo-4" d="m262.19,25.97h2.63l-9.64,24.35h-2.43l2.87-7.2-6.81-17.15h2.76l5.39,14.05,5.22-14.05Z"/>
49
+ <path class="iflogo-4" d="m263.09,43.52c-.46,0-.84-.16-1.15-.5-.31-.33-.46-.73-.46-1.21s.15-.88.46-1.21c.31-.33.69-.5,1.15-.5s.82.17,1.14.5c.32.33.48.73.48,1.21s-.16.88-.48,1.21c-.32.33-.7.5-1.14.5Z"/>
50
+ <path class="iflogo-4" d="m278.25,43.36l-.2-3.13c-.47,1.07-1.17,1.9-2.09,2.49-.92.59-2.09.89-3.5.89-1.21,0-2.25-.2-3.1-.59-.85-.39-1.51-.96-1.97-1.69-.46-.73-.69-1.59-.69-2.59,0-1.46.53-2.66,1.58-3.6,1.06-.94,2.58-1.51,4.58-1.72l5.19-.59v-1.39c0-.97-.35-1.76-1.04-2.35s-1.64-.89-2.83-.89-2.14.28-2.92.83c-.78.56-1.31,1.36-1.6,2.4l-2.29-.83c.45-1.53,1.28-2.72,2.48-3.58,1.2-.86,2.67-1.29,4.4-1.29,2,0,3.56.53,4.67,1.58,1.11,1.06,1.67,2.49,1.67,4.3v11.76h-2.33Zm-.2-8.31l-5.22.63c-1.12.14-1.98.46-2.56.96-.58.5-.88,1.15-.88,1.96s.29,1.46.86,1.95c.57.49,1.4.73,2.48.73,1.24,0,2.25-.25,3.03-.77.79-.51,1.37-1.2,1.74-2.07.37-.87.56-1.81.56-2.83v-.56Z"/>
51
+ <path class="iflogo-4" d="m284.92,22.49c-.52,0-.94-.17-1.28-.52-.34-.35-.51-.78-.51-1.29s.17-.97.51-1.32.76-.52,1.28-.52c.47,0,.88.17,1.23.52.35.35.52.79.52,1.32s-.17.94-.52,1.29c-.35.35-.76.52-1.23.52Zm-1.28,3.48h2.49v17.39h-2.49v-17.39Z"/>
52
+ </g>
53
+ </g>
54
+ </g>
55
+ </svg>
@@ -0,0 +1,215 @@
1
+ """Branded HTML for the loopback OAuth callback tab.
2
+
3
+ This tab is the frame straight after the Auth0 Universal Login widget, so it
4
+ copies that theme rather than the Interfaces app. Every value here mirrors
5
+ ``auth0_branding_theme.insight_factory`` in
6
+ ``terraform-Insightfactory-infrastructure/Auth0/modules/auth0/branding.tf``, and
7
+ the comments name the Terraform attribute each one came from. Change them together.
8
+
9
+ The page is self-contained: the logo is inline SVG and the CSS is inline, so it
10
+ renders from the loopback server with no network access beyond the webfont.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from functools import lru_cache
16
+ from importlib.resources import files
17
+
18
+
19
+ @lru_cache(maxsize=1)
20
+ def logo_svg() -> str:
21
+ """The wordmark Auth0 serves from the CDN, shipped with the package.
22
+
23
+ Auth0 loads it from cdn.insightfactory.ai. We inline the same file so the
24
+ callback page does not depend on the CDN being reachable.
25
+ """
26
+ return files("if_cli.assets").joinpath("insightfactoryai-logo.svg").read_text(encoding="utf-8")
27
+
28
+
29
+ STYLES = """\
30
+ @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap");
31
+
32
+ :root {
33
+ --page-background: #20252e; /* page_background.background_color */
34
+ --widget-background: #ffffff; /* colors.widget_background */
35
+ --header: #20252e; /* colors.header */
36
+ --body-text: #20252e; /* colors.body_text */
37
+ --icons: #65676e; /* colors.icons */
38
+ --input-border: #c9cace; /* colors.input_border */
39
+ --success: #13a688; /* colors.success */
40
+ --error: #d03c38; /* colors.error */
41
+ --success-soft: rgba(19, 166, 136, 0.12);
42
+ --error-soft: rgba(208, 60, 56, 0.12);
43
+ }
44
+
45
+ * {
46
+ box-sizing: border-box;
47
+ }
48
+
49
+ body {
50
+ margin: 0;
51
+ min-height: 100vh;
52
+ display: flex;
53
+ align-items: center; /* page_layout: center */
54
+ justify-content: center;
55
+ padding: 24px;
56
+ background: var(--page-background);
57
+ color: var(--body-text);
58
+ font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
59
+ font-size: 16px; /* fonts.reference_text_size */
60
+ font-weight: 300;
61
+ line-height: 1.5;
62
+ -webkit-font-smoothing: antialiased;
63
+ }
64
+
65
+ main {
66
+ width: 100%;
67
+ max-width: 400px;
68
+ padding: 40px 32px 32px;
69
+ border-radius: 8px; /* borders.widget_corner_radius */
70
+ background: var(--widget-background);
71
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); /* borders.show_widget_shadow */
72
+ text-align: center; /* widget.header_text_alignment */
73
+ }
74
+
75
+ .logo {
76
+ height: 68px; /* widget.logo_height */
77
+ width: auto;
78
+ max-width: 100%;
79
+ display: block;
80
+ margin: 0 auto 24px; /* widget.logo_position: center */
81
+ }
82
+
83
+ .pill {
84
+ display: inline-flex;
85
+ align-items: center;
86
+ gap: 7px;
87
+ margin-bottom: 16px;
88
+ padding: 4px 12px 4px 10px;
89
+ border-radius: 999px;
90
+ background: var(--pill-soft);
91
+ color: var(--pill);
92
+ font-size: 14px;
93
+ font-weight: 500;
94
+ }
95
+
96
+ .dot {
97
+ width: 7px;
98
+ height: 7px;
99
+ border-radius: 50%;
100
+ background: currentColor;
101
+ }
102
+
103
+ h1 {
104
+ margin: 0 0 10px;
105
+ color: var(--header); /* colors.header */
106
+ font-size: 24px; /* fonts.title: 150% of 16px */
107
+ font-weight: 300; /* fonts.title.bold = false */
108
+ line-height: 1.25;
109
+ }
110
+
111
+ p {
112
+ margin: 0;
113
+ color: var(--icons);
114
+ font-size: 14px; /* fonts.subtitle: 87.5% of 16px */
115
+ font-weight: 400;
116
+ }
117
+
118
+ code {
119
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
120
+ font-size: 13px;
121
+ color: var(--body-text);
122
+ }
123
+
124
+ pre {
125
+ margin: 20px 0 0;
126
+ padding: 12px 14px;
127
+ border: 1px solid var(--input-border); /* colors.input_border */
128
+ border-radius: 3px; /* borders.input_border_radius */
129
+ color: var(--body-text);
130
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
131
+ font-size: 13px;
132
+ text-align: left;
133
+ white-space: pre-wrap;
134
+ word-break: break-word;
135
+ }
136
+ """
137
+
138
+ _TEMPLATE = """\
139
+ <!DOCTYPE html>
140
+ <html lang="en">
141
+ <head>
142
+ <meta charset="utf-8">
143
+ <meta name="viewport" content="width=device-width, initial-scale=1">
144
+ <title>{title} | insightfactory.ai</title>
145
+ <style>
146
+ {styles}
147
+ </style>
148
+ </head>
149
+ <body>
150
+ <main style="--pill: var({pill_colour}); --pill-soft: var({pill_soft});">
151
+ {logo}
152
+ <span class="pill"><span class="dot"></span>{pill}</span>
153
+ <h1>{heading}</h1>
154
+ <p>{message}</p>
155
+ {detail}
156
+ </main>
157
+ </body>
158
+ </html>
159
+ """
160
+
161
+
162
+ def render(
163
+ *,
164
+ title: str,
165
+ pill: str,
166
+ heading: str,
167
+ message: str,
168
+ detail: str = "",
169
+ ok: bool = True,
170
+ ) -> bytes:
171
+ """Render one callback page, ready to write to the socket."""
172
+ return _TEMPLATE.format(
173
+ title=title,
174
+ styles=STYLES,
175
+ logo=logo_svg(),
176
+ pill=pill,
177
+ pill_colour="--success" if ok else "--error",
178
+ pill_soft="--success-soft" if ok else "--error-soft",
179
+ heading=heading,
180
+ message=message,
181
+ detail=detail,
182
+ ).encode("utf-8")
183
+
184
+
185
+ def success_page() -> bytes:
186
+ return render(
187
+ title="Login complete",
188
+ pill="Connected",
189
+ heading="Authentication successful",
190
+ message="You can close this tab and return to the terminal.",
191
+ )
192
+
193
+
194
+ def failure_page(error: str, description: str) -> bytes:
195
+ return render(
196
+ title="Login failed",
197
+ pill="Not connected",
198
+ heading="Authentication failed",
199
+ message="Close this tab and run <code>if-cli login</code> again.",
200
+ detail=f"<pre>{error}: {description}</pre>" if error or description else "",
201
+ ok=False,
202
+ )
203
+
204
+
205
+ def state_mismatch_page() -> bytes:
206
+ return render(
207
+ title="Login callback ignored",
208
+ pill="Not connected",
209
+ heading="Callback ignored",
210
+ message=(
211
+ "The callback state did not match the one this login started with, "
212
+ "so it was discarded. Close this tab and run <code>if-cli login</code> again."
213
+ ),
214
+ ok=False,
215
+ )
@@ -132,7 +132,8 @@ def mcp_command(argv: list[str]) -> None:
132
132
  # import bug inside if_cli.router propagates instead of reading as a missing extra.
133
133
  if importlib.util.find_spec("mcp") is None:
134
134
  die(MISSING_MCP_EXTRA)
135
- from if_cli.router.server import build_router, log, run_server
135
+ from if_cli.router.log import log
136
+ from if_cli.router.server import build_router, run_server
136
137
 
137
138
  timeout = parse_timeout_seconds(values["timeout"], "--timeout")
138
139
  extra_read_only = frozenset(values["allow-tool"] or [])
@@ -18,6 +18,7 @@ from if_cli.cache import (
18
18
  load_cache,
19
19
  store_tokens,
20
20
  )
21
+ from if_cli.callback_page import failure_page, state_mismatch_page, success_page
21
22
  from if_cli.config import Profile, meaningful
22
23
  from if_cli.constants import DEFAULT_SCOPE, LOOPBACK_HOST
23
24
  from if_cli.http import DEFAULT_REQUEST_TIMEOUT_S, fetch_with_timeout, validate_http_url
@@ -187,7 +188,7 @@ class _CallbackServer:
187
188
  self.send_header("Content-Type", "text/html; charset=utf-8")
188
189
  self.send_header("Connection", "close")
189
190
  self.end_headers()
190
- self.wfile.write(b"<h2>Login callback ignored because its state did not match.</h2>")
191
+ self.wfile.write(state_mismatch_page())
191
192
  return
192
193
  code = params.get("code")
193
194
  self.send_response(200)
@@ -195,12 +196,12 @@ class _CallbackServer:
195
196
  self.send_header("Connection", "close")
196
197
  self.end_headers()
197
198
  if code:
198
- body = "<h2>Login complete — you can close this tab and return to the terminal.</h2>"
199
+ body = success_page()
199
200
  else:
200
201
  error = escape_html(params.get("error") or "")
201
202
  description = escape_html(params.get("error_description") or "")
202
- body = f"<h2>Login failed.</h2><pre>{error}: {description}</pre>"
203
- self.wfile.write(body.encode("utf-8"))
203
+ body = failure_page(error, description)
204
+ self.wfile.write(body)
204
205
  callback.result = params
205
206
 
206
207
  return Handler
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+
6
+ def log(message: str) -> None:
7
+ """Write one diagnostic line to stderr.
8
+
9
+ The router speaks MCP over stdout, so stderr is the only channel that can
10
+ carry anything to the operator. This lives in its own module because both
11
+ `server` and `upstream` write to it and `server` imports `upstream`.
12
+ """
13
+ sys.stderr.write(f"[if-cli mcp] {message}\n")
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
- import sys
5
4
  from dataclasses import dataclass
6
5
  from importlib.metadata import version
7
6
  from typing import Any
@@ -15,6 +14,7 @@ from mcp.server.stdio import stdio_server
15
14
  from if_cli.config import Profile
16
15
  from if_cli.router import catalog, policy
17
16
  from if_cli.router.catalog import ENV_ARG, EnvironmentSpec
17
+ from if_cli.router.log import log
18
18
  from if_cli.router.upstream import Upstream
19
19
  from if_cli.runtime import CliError
20
20
 
@@ -24,10 +24,6 @@ SERVER_VERSION = version("insightfactory-cli")
24
24
  DEFAULT_CATALOG_RETRY_WINDOW_S = 5.0
25
25
 
26
26
 
27
- def log(message: str) -> None:
28
- sys.stderr.write(f"[if-cli mcp] {message}\n")
29
-
30
-
31
27
  def _catalog_retry_window_seconds() -> float:
32
28
  """Read the catalogue-rebuild retry window, in milliseconds, from the environment.
33
29