authsome 0.2.0rc14__tar.gz → 0.2.0rc15__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 (129) hide show
  1. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/PKG-INFO +1 -1
  2. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/pyproject.toml +1 -1
  3. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/__init__.py +19 -0
  4. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/github.json +3 -2
  5. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/klaviyo-oauth.json +2 -1
  6. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/sendgrid.json +2 -1
  7. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/flows/bridge.py +16 -0
  8. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/models/provider.py +1 -0
  9. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_auth_layer.py +43 -0
  10. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_bridge.py +8 -0
  11. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_models.py +15 -0
  12. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  14. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/release-please-config.json +0 -0
  15. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/release-please-manifest.json +0 -0
  16. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/workflows/pr-title.yml +0 -0
  17. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/workflows/publish-rc.yml +0 -0
  18. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/workflows/publish.yml +0 -0
  19. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/workflows/release-please.yml +0 -0
  20. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.github/workflows/test.yml +0 -0
  21. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.gitignore +0 -0
  22. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/.pre-commit-config.yaml +0 -0
  23. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/AGENTS.md +0 -0
  24. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/CHANGELOG.md +0 -0
  25. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/CLAUDE.md +0 -0
  26. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/CONTRIBUTING.md +0 -0
  27. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/LICENSE +0 -0
  28. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/README.md +0 -0
  29. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/docs/UBIQUITOUS_LANGUAGE.md +0 -0
  30. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/docs/architecture.md +0 -0
  31. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/docs/authsome-design.md +0 -0
  32. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/docs/cli.md +0 -0
  33. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/docs/manual-testing.md +0 -0
  34. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/docs/providers.md +0 -0
  35. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/docs/register-provider.md +0 -0
  36. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/skills/authsome/SKILL.md +0 -0
  37. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/skills/authsome/evals/evals.json +0 -0
  38. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/skills-lock.json +0 -0
  39. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/specs/authsome-v1.md +0 -0
  40. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/__init__.py +0 -0
  41. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/__init__.py +0 -0
  42. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/ahrefs.json +0 -0
  43. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/apollo.json +0 -0
  44. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/ashby.json +0 -0
  45. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/atlassian.json +0 -0
  46. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/beehiiv.json +0 -0
  47. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/brevo.json +0 -0
  48. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/buffer.json +0 -0
  49. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/calendly.json +0 -0
  50. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/clearbit.json +0 -0
  51. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/discord.json +0 -0
  52. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/dub.json +0 -0
  53. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/g2.json +0 -0
  54. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/gitlab.json +0 -0
  55. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/google.json +0 -0
  56. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/hubspot.json +0 -0
  57. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/hunter.json +0 -0
  58. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/instantly.json +0 -0
  59. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/intercom.json +0 -0
  60. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/keywords-everywhere.json +0 -0
  61. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/klaviyo.json +0 -0
  62. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/lemlist.json +0 -0
  63. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/linear.json +0 -0
  64. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/livestorm.json +0 -0
  65. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/mailchimp.json +0 -0
  66. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/mention-me.json +0 -0
  67. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/microsoft.json +0 -0
  68. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/notion.json +0 -0
  69. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/openai.json +0 -0
  70. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/optimizely.json +0 -0
  71. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/postiz.json +0 -0
  72. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/postmark.json +0 -0
  73. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/resend.json +0 -0
  74. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/rewardful.json +0 -0
  75. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/savvycal.json +0 -0
  76. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/semrush.json +0 -0
  77. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/slack.json +0 -0
  78. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/tolt.json +0 -0
  79. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/typeform.json +0 -0
  80. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/wistia.json +0 -0
  81. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/x.json +0 -0
  82. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/bundled_providers/zapier.json +0 -0
  83. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/flows/__init__.py +0 -0
  84. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/flows/api_key.py +0 -0
  85. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/flows/base.py +0 -0
  86. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/flows/dcr_pkce.py +0 -0
  87. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/flows/device_code.py +0 -0
  88. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/flows/pkce.py +0 -0
  89. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/input_provider.py +0 -0
  90. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/models/__init__.py +0 -0
  91. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/models/config.py +0 -0
  92. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/models/connection.py +0 -0
  93. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/models/enums.py +0 -0
  94. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/models/profile.py +0 -0
  95. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/providers/__init__.py +0 -0
  96. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/auth/providers/registry.py +0 -0
  97. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/bundled_providers/postiz.json +0 -0
  98. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/cli.py +0 -0
  99. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/context.py +0 -0
  100. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/errors.py +0 -0
  101. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/proxy/__init__.py +0 -0
  102. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/proxy/router.py +0 -0
  103. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/proxy/runner.py +0 -0
  104. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/proxy/server.py +0 -0
  105. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/py.typed +0 -0
  106. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/utils.py +0 -0
  107. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/vault/__init__.py +0 -0
  108. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/vault/crypto.py +0 -0
  109. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/src/authsome/vault/storage.py +0 -0
  110. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/suggestions.md +0 -0
  111. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/__init__.py +0 -0
  112. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/__init__.py +0 -0
  113. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_flows.py +0 -0
  114. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_flows_dcr_pkce.py +0 -0
  115. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_flows_device_code.py +0 -0
  116. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_flows_pkce.py +0 -0
  117. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_registry.py +0 -0
  118. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/auth/test_url_template.py +0 -0
  119. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/common/__init__.py +0 -0
  120. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/common/test_cli.py +0 -0
  121. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/common/test_errors.py +0 -0
  122. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/common/test_logging.py +0 -0
  123. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/common/test_utils.py +0 -0
  124. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/proxy/__init__.py +0 -0
  125. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/proxy/test_proxy.py +0 -0
  126. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/vault/__init__.py +0 -0
  127. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/vault/test_crypto.py +0 -0
  128. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/tests/vault/test_store.py +0 -0
  129. {authsome-0.2.0rc14 → authsome-0.2.0rc15}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: authsome
3
- Version: 0.2.0rc14
3
+ Version: 0.2.0rc15
4
4
  Summary: A portable local authentication library for AI agents and developer tools
5
5
  Author-email: Manoj Bajaj <manojbajaj95@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "authsome"
7
- version = "0.2.0rc14"
7
+ version = "0.2.0rc15"
8
8
  description = "A portable local authentication library for AI agents and developer tools"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -227,6 +227,8 @@ class AuthLayer:
227
227
  if flow_type == FlowType.API_KEY:
228
228
  fields_to_collect.append(InputField(name="api_key", label="API Key", secret=True))
229
229
 
230
+ static_hints.extend(self._build_docs_hints(definition, flow_type))
231
+
230
232
  if fields_to_collect:
231
233
  ip: InputProvider = input_provider or BridgeInputProvider(
232
234
  title=f"{definition.display_name} Credentials",
@@ -289,6 +291,23 @@ class AuthLayer:
289
291
  logger.info("Login successful: provider={} connection={} profile={}", provider, connection_name, self._identity)
290
292
  return result.connection
291
293
 
294
+ @staticmethod
295
+ def _build_docs_hints(definition: ProviderDefinition, flow_type: FlowType) -> list[dict[str, Any]]:
296
+ """Convert provider docs URL into a bridge instruction block."""
297
+ if not definition.docs:
298
+ return []
299
+
300
+ if flow_type not in (FlowType.PKCE, FlowType.DEVICE_CODE, FlowType.DCR_PKCE, FlowType.API_KEY):
301
+ return []
302
+
303
+ return [
304
+ {
305
+ "type": "instructions",
306
+ "label": "Instructions",
307
+ "url": definition.docs,
308
+ }
309
+ ]
310
+
292
311
  # ── Token operations ──────────────────────────────────────────────────
293
312
 
294
313
  def get_access_token(self, provider: str, connection: str = "default") -> str:
@@ -24,5 +24,6 @@
24
24
  "access_token": "GITHUB_ACCESS_TOKEN",
25
25
  "refresh_token": "GITHUB_REFRESH_TOKEN"
26
26
  }
27
- }
28
- }
27
+ },
28
+ "docs": "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens"
29
+ }
@@ -21,5 +21,6 @@
21
21
  "access_token": "KLAVIYO_OAUTH_ACCESS_TOKEN",
22
22
  "refresh_token": "KLAVIYO_OAUTH_REFRESH_TOKEN"
23
23
  }
24
- }
24
+ },
25
+ "docs": "https://developers.klaviyo.com/en/docs/manage_your_app"
25
26
  }
@@ -13,5 +13,6 @@
13
13
  "env": {
14
14
  "api_key": "SENDGRID_API_KEY"
15
15
  }
16
- }
16
+ },
17
+ "docs": "https://www.twilio.com/docs/sendgrid/ui/account-and-settings/api-keys"
17
18
  }
@@ -46,6 +46,10 @@ class _BridgeHandler(http.server.BaseHTTPRequestHandler):
46
46
  "border-radius: 4px; cursor: pointer; font-size: 16px; }",
47
47
  "button:hover { background-color: #0052a3; }",
48
48
  "button.copybtn { width: auto; padding: 8px 12px; font-size: 14px; flex-shrink: 0; }",
49
+ ".instructions { margin-bottom: 16px; padding: 12px; border: 1px solid #ddd; border-radius: 8px; }",
50
+ ".instructions-title { margin: 0 0 8px; font-weight: 600; }",
51
+ ".instructions-links { margin: 0; padding-left: 20px; }",
52
+ ".instructions-links li { margin-bottom: 6px; }",
49
53
  "</style>",
50
54
  "</head><body>",
51
55
  f"<h2>{self.title}</h2>",
@@ -54,6 +58,18 @@ class _BridgeHandler(http.server.BaseHTTPRequestHandler):
54
58
 
55
59
  for field in self.fields:
56
60
  label = field["label"]
61
+ if field.get("type") == "instructions":
62
+ url = field.get("url")
63
+ if url:
64
+ html.append("<div class='instructions'>")
65
+ html.append(f"<p class='instructions-title'>{escape(label)}</p>")
66
+ url_esc = escape(url, quote=True)
67
+ html.append(
68
+ "<ul class='instructions-links'>"
69
+ f"<li><a href='{url_esc}' target='_blank' rel='noopener noreferrer'>Read setup docs</a></li>"
70
+ "</ul></div>"
71
+ )
72
+ continue
57
73
  if field.get("type") == "static":
58
74
  val = field.get("value", "")
59
75
  val_esc = escape(val, quote=True)
@@ -61,6 +61,7 @@ class ProviderDefinition(BaseModel):
61
61
  oauth: OAuthConfig | None = None
62
62
  api_key: ApiKeyConfig | None = None
63
63
  export: ExportConfig | None = None
64
+ docs: str | None = None
64
65
  host_url: str | None = None
65
66
 
66
67
  metadata: dict[str, Any] = Field(default_factory=dict)
@@ -282,6 +282,49 @@ class TestAuthLayerLogin:
282
282
  assert creds.client_id == "cid"
283
283
  assert creds.client_secret is None
284
284
 
285
+ def test_login_api_key_bridge_includes_docs_instructions(self, auth: AuthLayer) -> None:
286
+ from authsome.auth.flows.base import FlowResult
287
+
288
+ provider = ProviderDefinition.model_validate(
289
+ {
290
+ "name": "docsapi",
291
+ "display_name": "Docs API",
292
+ "auth_type": "api_key",
293
+ "flow": "api_key",
294
+ "api_key": {"header_name": "Authorization", "header_prefix": "Bearer"},
295
+ "docs": "https://example.com/create-key",
296
+ }
297
+ )
298
+ auth.register_provider(provider)
299
+
300
+ mock_record = ConnectionRecord(
301
+ schema_version=2,
302
+ provider="docsapi",
303
+ profile="default",
304
+ connection_name="default",
305
+ auth_type=AuthType.API_KEY,
306
+ status=ConnectionStatus.CONNECTED,
307
+ api_key="sk-123",
308
+ )
309
+
310
+ with patch("authsome.auth._FLOW_HANDLERS") as handlers:
311
+ mock_handler = MagicMock()
312
+ mock_handler.authenticate.return_value = FlowResult(connection=mock_record)
313
+ handlers.get.return_value = lambda: mock_handler
314
+
315
+ with patch("authsome.auth.BridgeInputProvider") as bridge_cls:
316
+ bridge_instance = MagicMock()
317
+ bridge_instance.collect.return_value = {"api_key": "sk-123"}
318
+ bridge_cls.return_value = bridge_instance
319
+
320
+ auth.login("docsapi")
321
+
322
+ static_fields = bridge_cls.call_args.kwargs["static_fields"]
323
+ assert len(static_fields) == 1
324
+ assert static_fields[0]["type"] == "instructions"
325
+ assert static_fields[0]["label"] == "Instructions"
326
+ assert static_fields[0]["url"] == "https://example.com/create-key"
327
+
285
328
 
286
329
  class TestAuthLayerCredentials:
287
330
  """Credential retrieval tests."""
@@ -17,6 +17,11 @@ def test_find_free_port():
17
17
 
18
18
  def test_secure_input_bridge_success():
19
19
  fields = [
20
+ {
21
+ "type": "instructions",
22
+ "label": "Instructions",
23
+ "url": "https://example.com/oauth-app",
24
+ },
20
25
  {"name": "api_key", "label": "API Key", "type": "password"},
21
26
  {"name": "username", "label": "Username", "required": False},
22
27
  ]
@@ -28,6 +33,9 @@ def test_secure_input_bridge_success():
28
33
  assert response.status == 200
29
34
  html = response.read().decode("utf-8")
30
35
  assert "Test Auth" in html
36
+ assert "Instructions" in html
37
+ assert "Read setup docs" in html
38
+ assert "https://example.com/oauth-app" in html
31
39
  assert "API Key" in html
32
40
  assert "Username" in html
33
41
 
@@ -123,6 +123,21 @@ class TestProviderDefinition:
123
123
  restored = ProviderDefinition.model_validate_json(json_str)
124
124
  assert restored.name == "test"
125
125
 
126
+ def test_docs_field_is_optional_and_parsed(self) -> None:
127
+ provider = ProviderDefinition.model_validate(
128
+ {
129
+ "schema_version": 1,
130
+ "name": "calendly",
131
+ "display_name": "Calendly",
132
+ "auth_type": "api_key",
133
+ "flow": "api_key",
134
+ "api_key": {"header_name": "Authorization", "header_prefix": "Bearer"},
135
+ "docs": "https://example.com/setup",
136
+ }
137
+ )
138
+
139
+ assert provider.docs == "https://example.com/setup"
140
+
126
141
 
127
142
  class TestSensitiveAnnotation:
128
143
  """Sensitive field annotation tests."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes