ai2web 0.1.1 → 0.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 892b44b396f60be4ebd7af6bca0c19be6b1c3d8fdbdd2a66b2ed9235915c8690
4
- data.tar.gz: a6705de41a0928bdddcf5787b13b72b53f0d45b6e47c4805deff898e5c650fad
3
+ metadata.gz: 1fe0e944f49b6072c4ae055b3d2db69492580564a0b2ea05f0f3ecf76bc98474
4
+ data.tar.gz: 62c79d2ecfc8f24a9351ba4cb6d82868f7c1877f2e1c16dd060dc1c2b3997384
5
5
  SHA512:
6
- metadata.gz: 675f6db1358bf674c7579b27db5b5ca46f4e44be3cd76abacf793f4e10a25ef4f2b83fff596bf8bd65010476004c6c3e7b629e31344e6b8ee47de50fca91c1d6
7
- data.tar.gz: df4a693934c5e3838a2e016034a79d4ca838295a0842d77117fbd52ba1fe17551ac50791e9dc156c4830a896633bd31a12802d58f0b14901cad6b6b24673eb3d
6
+ metadata.gz: 809c65cb23a78846e260ff419aecabcc99eb22ab03802349bf22c51a90b9d513abbe5f5336d6792e727aa11c1dc1f604bc4508281a6a0253490c6cd3363a94af
7
+ data.tar.gz: 732da62ffbf60019317538d5fbcc56c74bcc73291c9e6d65afd09b073aced6d35bdd392d19198a94289dbebd1c34796b32ae17bfa5293e2e3109c5d2b7f31bea
data/lib/ai2web/export.rb CHANGED
@@ -87,10 +87,84 @@ module Ai2Web
87
87
  }
88
88
  }
89
89
  end
90
+
91
+ # OAuth 2.0 Protected Resource metadata (RFC 9728), for
92
+ # /.well-known/oauth-protected-resource. MCP clients read this to discover which
93
+ # authorization server guards the resource before starting a flow.
94
+ #
95
+ # Returns nil when the site does not advertise oauth2, so an auth surface the site cannot
96
+ # honour is never published.
97
+ def to_oauth_protected_resource(manifest)
98
+ m = Util.deep_stringify(manifest)
99
+ auth = m["auth"].is_a?(Hash) ? m["auth"] : {}
100
+ return nil unless Array(auth["methods"]).include?("oauth2")
101
+
102
+ site = m["site"].is_a?(Hash) ? m["site"] : {}
103
+ base = Util.trim_url(site["url"].to_s)
104
+ oauth2 = auth["oauth2"].is_a?(Hash) ? auth["oauth2"] : {}
105
+ issuer = base
106
+ authz = oauth2["authorization_url"].to_s
107
+ unless authz.empty?
108
+ begin
109
+ u = URI.parse(authz)
110
+ issuer = "#{u.scheme}://#{u.host}#{u.port && ![80, 443].include?(u.port) ? ":#{u.port}" : ""}" if u.scheme && u.host
111
+ rescue URI::InvalidURIError
112
+ # keep the site base as issuer
113
+ end
114
+ end
115
+ doc = {
116
+ "resource" => "#{base}/ai2w",
117
+ "authorization_servers" => [issuer],
118
+ "bearer_methods_supported" => ["header"]
119
+ }
120
+ scopes = oauth2["scopes"]
121
+ doc["scopes_supported"] = Array(scopes) if scopes && !Array(scopes).empty?
122
+ doc
123
+ end
124
+
125
+ # Map usage_policy onto Content Signals tokens. `search` stays yes because AI2Web exists to
126
+ # be discoverable; the AI signals are only asserted when the manifest states them, so an
127
+ # unset policy is never reported as a refusal. Nil when no policy is declared.
128
+ def to_content_signals(manifest)
129
+ m = Util.deep_stringify(manifest)
130
+ p = m["usage_policy"]
131
+ return nil unless p.is_a?(Hash) && !p.empty?
132
+
133
+ signals = ["search=yes"]
134
+ signals << "ai-input=#{p["content_reproduction"] ? "yes" : "no"}" if [true, false].include?(p["content_reproduction"])
135
+ signals << "ai-train=#{p["model_training"] ? "yes" : "no"}" if [true, false].include?(p["model_training"])
136
+ signals.join(", ")
137
+ end
138
+
139
+ # A robots.txt FRAGMENT carrying the usage policy and a pointer to the manifest. Append it to
140
+ # an existing robots.txt; it is never a replacement, and emits no Disallow rules.
141
+ def to_robots_txt(manifest)
142
+ m = Util.deep_stringify(manifest)
143
+ site = m["site"].is_a?(Hash) ? m["site"] : {}
144
+ base = Util.trim_url(site["url"].to_s)
145
+ lines = ["# AI2Web usage policy, projected from #{base}/ai2w", "User-agent: *"]
146
+ signals = to_content_signals(m)
147
+ lines << "Content-Signal: #{signals}" unless signals.nil?
148
+ lines << "# bulk_extraction: false - please use the /ai2w endpoints instead of crawling" if
149
+ m["usage_policy"].is_a?(Hash) && m["usage_policy"]["bulk_extraction"] == false
150
+ lines << "# AI2Web-Manifest: #{base}/ai2w"
151
+ "#{lines.join("\n")}\n"
152
+ end
153
+
154
+ # Value for an HTTP Link header advertising the manifest to non-HTML clients.
155
+ def to_discovery_link_header(manifest)
156
+ m = Util.deep_stringify(manifest)
157
+ site = m["site"].is_a?(Hash) ? m["site"] : {}
158
+ "<#{Util.trim_url(site["url"].to_s)}/ai2w>; rel=\"ai2w\""
159
+ end
90
160
  end
91
161
 
92
162
  module_function
93
163
 
94
164
  def to_llms_txt(manifest) = Export.to_llms_txt(manifest)
95
165
  def to_agent_json(manifest) = Export.to_agent_json(manifest)
166
+ def to_oauth_protected_resource(manifest) = Export.to_oauth_protected_resource(manifest)
167
+ def to_content_signals(manifest) = Export.to_content_signals(manifest)
168
+ def to_robots_txt(manifest) = Export.to_robots_txt(manifest)
169
+ def to_discovery_link_header(manifest) = Export.to_discovery_link_header(manifest)
96
170
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ai2Web
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ai2web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - AI2Web Foundation
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-21 00:00:00.000000000 Z
11
+ date: 2026-07-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The Ruby implementation of the AI2Web protocol - describe your website
14
14
  once and make it understandable and actionable to any AI agent. Fluent manifest