Commit

Author:

Hash:

Timestamp:

+1803 -0 +/-27 browse

Kevin Schoon [me@kevinschoon.com]

f441045a7d94c19ee7d43b8f7725df726d42dd89

Sun, 07 Dec 2025 17:39:50 +0000 (11 hours ago)

automated www update
1diff --git a/public/404.html b/public/404.html
2new file mode 100644
3index 0000000..f8414f0
4--- /dev/null
5+++ b/public/404.html
6 @@ -0,0 +1,3 @@
7+ <!doctype html>
8+ <title>404 Not Found</title>
9+ <h1>404 Not Found</h1>
10 diff --git a/public/LICENSE.txt b/public/LICENSE.txt
11new file mode 100644
12index 0000000..0e259d4
13--- /dev/null
14+++ b/public/LICENSE.txt
15 @@ -0,0 +1,121 @@
16+ Creative Commons Legal Code
17+
18+ CC0 1.0 Universal
19+
20+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
21+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
22+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
23+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
24+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
25+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
26+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
27+ HEREUNDER.
28+
29+ Statement of Purpose
30+
31+ The laws of most jurisdictions throughout the world automatically confer
32+ exclusive Copyright and Related Rights (defined below) upon the creator
33+ and subsequent owner(s) (each and all, an "owner") of an original work of
34+ authorship and/or a database (each, a "Work").
35+
36+ Certain owners wish to permanently relinquish those rights to a Work for
37+ the purpose of contributing to a commons of creative, cultural and
38+ scientific works ("Commons") that the public can reliably and without fear
39+ of later claims of infringement build upon, modify, incorporate in other
40+ works, reuse and redistribute as freely as possible in any form whatsoever
41+ and for any purposes, including without limitation commercial purposes.
42+ These owners may contribute to the Commons to promote the ideal of a free
43+ culture and the further production of creative, cultural and scientific
44+ works, or to gain reputation or greater distribution for their Work in
45+ part through the use and efforts of others.
46+
47+ For these and/or other purposes and motivations, and without any
48+ expectation of additional consideration or compensation, the person
49+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
50+ is an owner of Copyright and Related Rights in the Work, voluntarily
51+ elects to apply CC0 to the Work and publicly distribute the Work under its
52+ terms, with knowledge of his or her Copyright and Related Rights in the
53+ Work and the meaning and intended legal effect of CC0 on those rights.
54+
55+ 1. Copyright and Related Rights. A Work made available under CC0 may be
56+ protected by copyright and related or neighboring rights ("Copyright and
57+ Related Rights"). Copyright and Related Rights include, but are not
58+ limited to, the following:
59+
60+ i. the right to reproduce, adapt, distribute, perform, display,
61+ communicate, and translate a Work;
62+ ii. moral rights retained by the original author(s) and/or performer(s);
63+ iii. publicity and privacy rights pertaining to a person's image or
64+ likeness depicted in a Work;
65+ iv. rights protecting against unfair competition in regards to a Work,
66+ subject to the limitations in paragraph 4(a), below;
67+ v. rights protecting the extraction, dissemination, use and reuse of data
68+ in a Work;
69+ vi. database rights (such as those arising under Directive 96/9/EC of the
70+ European Parliament and of the Council of 11 March 1996 on the legal
71+ protection of databases, and under any national implementation
72+ thereof, including any amended or successor version of such
73+ directive); and
74+ vii. other similar, equivalent or corresponding rights throughout the
75+ world based on applicable law or treaty, and any national
76+ implementations thereof.
77+
78+ 2. Waiver. To the greatest extent permitted by, but not in contravention
79+ of, applicable law, Affirmer hereby overtly, fully, permanently,
80+ irrevocably and unconditionally waives, abandons, and surrenders all of
81+ Affirmer's Copyright and Related Rights and associated claims and causes
82+ of action, whether now known or unknown (including existing as well as
83+ future claims and causes of action), in the Work (i) in all territories
84+ worldwide, (ii) for the maximum duration provided by applicable law or
85+ treaty (including future time extensions), (iii) in any current or future
86+ medium and for any number of copies, and (iv) for any purpose whatsoever,
87+ including without limitation commercial, advertising or promotional
88+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
89+ member of the public at large and to the detriment of Affirmer's heirs and
90+ successors, fully intending that such Waiver shall not be subject to
91+ revocation, rescission, cancellation, termination, or any other legal or
92+ equitable action to disrupt the quiet enjoyment of the Work by the public
93+ as contemplated by Affirmer's express Statement of Purpose.
94+
95+ 3. Public License Fallback. Should any part of the Waiver for any reason
96+ be judged legally invalid or ineffective under applicable law, then the
97+ Waiver shall be preserved to the maximum extent permitted taking into
98+ account Affirmer's express Statement of Purpose. In addition, to the
99+ extent the Waiver is so judged Affirmer hereby grants to each affected
100+ person a royalty-free, non transferable, non sublicensable, non exclusive,
101+ irrevocable and unconditional license to exercise Affirmer's Copyright and
102+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
103+ maximum duration provided by applicable law or treaty (including future
104+ time extensions), (iii) in any current or future medium and for any number
105+ of copies, and (iv) for any purpose whatsoever, including without
106+ limitation commercial, advertising or promotional purposes (the
107+ "License"). The License shall be deemed effective as of the date CC0 was
108+ applied by Affirmer to the Work. Should any part of the License for any
109+ reason be judged legally invalid or ineffective under applicable law, such
110+ partial invalidity or ineffectiveness shall not invalidate the remainder
111+ of the License, and in such case Affirmer hereby affirms that he or she
112+ will not (i) exercise any of his or her remaining Copyright and Related
113+ Rights in the Work or (ii) assert any associated claims and causes of
114+ action with respect to the Work, in either case contrary to Affirmer's
115+ express Statement of Purpose.
116+
117+ 4. Limitations and Disclaimers.
118+
119+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
120+ surrendered, licensed or otherwise affected by this document.
121+ b. Affirmer offers the Work as-is and makes no representations or
122+ warranties of any kind concerning the Work, express, implied,
123+ statutory or otherwise, including without limitation warranties of
124+ title, merchantability, fitness for a particular purpose, non
125+ infringement, or the absence of latent or other defects, accuracy, or
126+ the present or absence of errors, whether or not discoverable, all to
127+ the greatest extent permissible under applicable law.
128+ c. Affirmer disclaims responsibility for clearing rights of other persons
129+ that may apply to the Work or any use thereof, including without
130+ limitation any person's Copyright and Related Rights in the Work.
131+ Further, Affirmer disclaims responsibility for obtaining any necessary
132+ consents, permissions or other rights required for any use of the
133+ Work.
134+ d. Affirmer understands and acknowledges that Creative Commons is not a
135+ party to this document and has no duty or obligation with respect to
136+ this CC0 or use of the Work.
137 diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png
138new file mode 100644
139index 0000000..f6137eb
140 Binary files /dev/null and b/public/android-chrome-192x192.png differ
141 diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png
142new file mode 100644
143index 0000000..c77c2af
144 Binary files /dev/null and b/public/android-chrome-512x512.png differ
145 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
146new file mode 100644
147index 0000000..aaeaa8d
148 Binary files /dev/null and b/public/apple-touch-icon.png differ
149 diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png
150new file mode 100644
151index 0000000..29dd09f
152 Binary files /dev/null and b/public/favicon-16x16.png differ
153 diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png
154new file mode 100644
155index 0000000..811ba56
156 Binary files /dev/null and b/public/favicon-32x32.png differ
157 diff --git a/public/favicon.ico b/public/favicon.ico
158new file mode 100644
159index 0000000..de83549
160 Binary files /dev/null and b/public/favicon.ico differ
161 diff --git a/public/img/cc0.png b/public/img/cc0.png
162new file mode 100644
163index 0000000..df2c82f
164 Binary files /dev/null and b/public/img/cc0.png differ
165 diff --git a/public/img/codeberg.svg b/public/img/codeberg.svg
166new file mode 100644
167index 0000000..068702a
168--- /dev/null
169+++ b/public/img/codeberg.svg
170 @@ -0,0 +1,19 @@
171+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg-badge" height="168" width="564" viewBox="0 0 564 168">
172+ <defs>
173+ <linearGradient xlink:href="#a" id="b" gradientUnits="userSpaceOnUse" x1="42519.285" y1="-7078.789" x2="42575.336" y2="-6966.931" />
174+ <linearGradient id="a">
175+ <stop offset="0" stop-color="#fff" stop-opacity="0" />
176+ <stop offset=".495" stop-color="#71c2ff" />
177+ <stop offset="1" stop-color="#39aaff" />
178+ </linearGradient>
179+ </defs>
180+ <rect id="rect" ry="15.111" y="2" x="2" height="164" width="560" fill="none" stroke="#a6a6a6" stroke-width="4" stroke-linejoin="round" paint-order="fill stroke markers" />
181+ <text style="line-height:0%;-inkscape-font-specification:'Inter';marker:none" x="203.16" y="-85.813" transform="matrix(1.0087 0 0 .99138 -19.194 133.848)" color="#000" font-weight="400" font-size="11.814" font-family="Inter, 'DejaVu Sans', sans-serif" letter-spacing="0" word-spacing="0" overflow="visible" fill="#2185d0" stroke-width=".985">
182+ <tspan id="claim-svg" style="line-height:1;-inkscape-font-specification:'Inter'" x="203.16" y="-85.813" font-size="33.596">GET IT ON</tspan>
183+ </text>
184+ <path aria-label="Codeberg" d="M214.654 71.02c-8.453 0-15.047 2.79-19.803 8.369-4.803 5.577-7.215 12.943-7.215 22.096 0 7.034 1.764 13.158 5.281 18.372 4.862 7.275 12.274 10.912 22.2 10.912 6.907 0 12.922-2.516 18.049-7.548l-4.718-7.185c-4.57 2.85-8.512 4.275-11.844 4.275-5.069 0-9.112-1.91-12.12-5.729-2.847-3.638-4.271-8.004-4.271-13.097 0-5.76 1.211-10.428 3.613-14.005 2.773-4.183 6.694-6.274 11.747-6.274 3.65 0 7.684 1.574 12.11 4.728l4.734-7.546c-5.925-4.91-11.844-7.368-17.763-7.368zm167.7 0l-11.657 6.456v52.201h9.441l.925-4.091c2.843 3.455 6.844 5.183 12.03 5.183 5.238 0 9.638-1.548 13.224-4.64 4.56-4.061 6.85-10.033 6.85-17.914 0-6.122-1.547-11.033-4.634-14.732-3.523-4.184-8.38-6.275-14.612-6.275-2.226 0-4.266.418-6.105 1.274-2.348 1.09-4.176 2.607-5.462 4.546zm-62.745.183l-11.674 6.367v13.186c-2.895-2.304-6.471-3.455-10.722-3.455-4.93 0-9.16 1.606-12.667 4.82-4.692 4.304-7.056 10.427-7.056 18.37 0 5.638 1.466 10.247 4.362 13.824 3.454 4.304 8.486 6.456 15.085 6.456 2.343 0 4.622-.517 6.838-1.547 2.232-1.03 3.83-2.334 4.825-3.909l1.663 4.364h9.346zm-65.647 16.098c-6.057 0-10.956 1.878-14.718 5.636-4.139 4.123-6.206 9.52-6.206 16.188 0 5.88 1.855 10.854 5.553 14.916 4.134 4.486 9.272 6.73 15.371 6.73 5.255 0 9.84-1.759 13.783-5.275 4.75-4.183 7.125-9.641 7.125-16.371 0-5.758-1.599-10.638-4.808-14.641-3.826-4.79-9.192-7.183-16.1-7.183zm91.746 0c-5.988 0-10.914 1.94-14.819 5.82-4.314 4.245-6.477 9.609-6.477 16.096 0 6.61 1.923 11.792 5.744 15.552 4.017 4 9.596 6.002 16.758 6.002 6.647 0 12.205-1.94 16.641-5.822l-2.964-6.092c-4.368 2.426-8.56 3.637-12.572 3.637-2.842 0-5.302-.908-7.401-2.728-2.051-1.88-3.194-4.214-3.417-7.001h28.852v-4.183c0-6.003-1.6-10.885-4.809-14.643-3.693-4.427-8.884-6.638-15.536-6.638zm92.543 0c-5.978 0-10.909 1.94-14.803 5.82-4.326 4.245-6.472 9.609-6.472 16.096 0 6.61 1.907 11.792 5.733 15.552 4 4 9.585 6.002 16.753 6.002 6.658 0 12.205-1.94 16.652-5.822l-2.954-6.092c-4.389 2.426-8.587 3.637-12.588 3.637-2.837 0-5.308-.908-7.406-2.728-2.04-1.88-3.183-4.214-3.427-7.001h28.878v-4.183c0-6.003-1.605-10.885-4.814-14.643-3.709-4.427-8.879-6.638-15.552-6.638zm45.163 0c-1.838 0-3.672.485-5.462 1.455-1.727.908-2.901 2-3.512 3.272l-1.11-3.635h-9.9v41.286h11.477V100.03c.978-2.12 2.524-3.18 4.623-3.18 2.28 0 4.442 1.15 6.466 3.454l9.814-5.274c-2.71-5.154-6.844-7.73-12.396-7.73zm30.121 0c-4.883 0-9.107 1.606-12.683 4.82-4.691 4.304-7.024 10.427-7.024 18.37 0 5.577 1.435 10.186 4.336 13.824 3.4 4.304 8.188 6.456 14.351 6.456 5.064 0 8.974-2.032 11.759-6.094 0 9.094-3.342 13.642-10 13.642-4.697 0-8.921-1.213-12.683-3.638l-2.954 5.82c4.856 4.365 10.637 6.549 17.295 6.549 6.413 0 11.354-1.85 14.813-5.549 3.502-3.758 5.266-9.125 5.255-16.098v-37.01h-8.974l-.914 4.455c0-.604-.893-1.515-2.688-2.728-2.716-1.879-6.005-2.82-9.889-2.82zm-167.923 8.094c5.553 0 8.496 2.97 8.794 8.912h-17.869c.675-5.942 3.704-8.912 9.075-8.912zm92.554 0c5.547 0 8.474 2.97 8.788 8.912h-17.858c.674-5.942 3.703-8.912 9.07-8.912zm-184.204.089c5.989 0 8.98 4.547 8.98 13.64 0 8.732-2.991 13.097-8.98 13.097-5.982 0-8.98-4.365-8.98-13.096 0-9.094 2.998-13.641 8.98-13.641zm45.748 0c2.04 0 3.794.67 5.282 2.002 1.53 1.272 2.518 2.908 2.943 4.909v11.55c-.318 2.243-1.408 4.243-3.315 6.002-1.913 1.698-3.985 2.547-6.2 2.547-5.989 0-8.98-4.516-8.98-13.55 0-4.123.924-7.396 2.778-9.82 1.85-2.426 4.347-3.64 7.492-3.64zm216.312 0c1.982 0 3.762.7 5.377 2.093 1.669 1.335 2.625 2.94 2.875 4.818v10.46c0 2.607-.935 4.878-2.785 6.82-1.854 1.878-4.043 2.819-6.572 2.819-6.047 0-9.075-4.516-9.075-13.55 0-4.123.924-7.396 2.779-9.82 1.849-2.426 4.325-3.64 7.401-3.64zm-124.322.094c2.832 0 5.048 1.211 6.663 3.637 1.594 2.425 2.407 5.728 2.407 9.912 0 4.244-.866 7.58-2.598 10.006-1.727 2.424-4.07 3.637-7.019 3.637-2.295 0-4.31-.76-6.03-2.274-1.68-1.518-2.604-3.396-2.769-5.637v-9.916c0-2.606.893-4.818 2.673-6.637 1.854-1.817 4.075-2.728 6.673-2.728z" style="line-height:1.25;-inkscape-font-specification:'Tajawal Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal" font-weight="700" font-size="17.899" font-family="Tajawal" letter-spacing="-.285" word-spacing="0" fill="#2185d0" />
185+ <g paint-order="stroke markers fill">
186+ <path style="font-variation-settings:normal" d="M42519.285-7078.79a.76.568 0 00-.738.675l33.586 125.888a87.182 87.182 0 0039.381-33.763l-71.565-92.52a.76.568 0 00-.664-.28z" transform="matrix(.75692 0 0 .74393 -32088.397 5317.32)" opacity=".5" fill="url(#b)" />
187+ <path d="M93.742 20.95A65.99 64.857 0 0028 85.807a65.99 64.857 0 0010.076 34.447l55.019-69.909a1.03.756 0 011.785 0l55.022 69.912a65.99 64.857 0 0010.078-34.45A65.99 64.857 0 0093.99 20.95a65.99 64.857 0 00-.248 0z" fill="#2185d0" />
188+ </g>
189+ </svg>
190\ No newline at end of file
191 diff --git a/public/img/logo.png b/public/img/logo.png
192new file mode 100644
193index 0000000..93e608e
194 Binary files /dev/null and b/public/img/logo.png differ
195 diff --git a/public/img/logo.svg b/public/img/logo.svg
196new file mode 100644
197index 0000000..cdc0267
198--- /dev/null
199+++ b/public/img/logo.svg
200 @@ -0,0 +1 @@
201+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><g transform="translate(0 -852.36)"><rect width="180" height="180" x="10" y="862.36" rx="8.546" ry="7.52" style="fill-opacity:.24215;fill:#e7eab7"/><path d="M159.21 52.277c0 12.686-11.082 22.97-24.752 22.97s-24.752-10.284-24.752-22.97 11.082-22.97 24.752-22.97 24.752 10.284 24.752 22.97z" style="fill-opacity:.89686;fill:#b3b3b3" transform="matrix(.6464 0 0 .69655 -38.912 967.95)"/><path d="M118.91 1020.4c.102-.453.25-.888.344-1.344a72.568 72.568 0 0 0 1.094-7.219c.248-2.444.375-4.927.375-7.437s-.127-4.993-.375-7.438-.61-4.851-1.094-7.218a72.281 72.281 0 0 0-1.813-6.97 72.276 72.276 0 0 0-2.437-6.687c-.92-2.175-1.943-4.283-3.063-6.343s-2.318-4.066-3.625-6-2.705-3.798-4.187-5.594a73.377 73.377 0 0 0-4.72-5.156c-1.644-1.645-3.36-3.237-5.155-4.72s-3.66-2.88-5.594-4.187c-1.934-1.306-3.94-2.505-6-3.625s-4.169-2.142-6.344-3.062a72.255 72.255 0 0 0-6.687-2.438 72.245 72.245 0 0 0-6.97-1.812 72.57 72.57 0 0 0-7.218-1.094c-2.445-.248-4.927-.375-7.437-.375s-4.993.127-7.438.375-4.852.61-7.219 1.094c-.456.093-.89.242-1.344.344v25.5c1.704-.552 3.345-1.23 5.125-1.594 1.595-.327 3.228-.583 4.875-.75s3.309-.25 5-.25 3.353.082 5 .25c1.648.167 3.28.423 4.875.75 3.19.652 6.257 1.604 9.188 2.844s5.737 2.77 8.344 4.53a49.125 49.125 0 0 1 7.25 5.97 49.138 49.138 0 0 1 5.969 7.25 49.135 49.135 0 0 1 4.53 8.343 48.502 48.502 0 0 1 2.845 9.188c.326 1.595.582 3.228.75 4.875s.25 3.308.25 5c0 1.691-.083 3.353-.25 5a49.126 49.126 0 0 1-.75 4.875c-.365 1.78-1.043 3.42-1.594 5.125h25.5z" style="fill-opacity:.89686;fill:#b3b3b3"/><path d="M48 874.36c-4.487 0-8.911.212-13.281.656-.92.094-1.805.294-2.719.407v29.562c1.894-.303 3.76-.68 5.688-.875 3.388-.345 6.833-.532 10.312-.532 3.478 0 6.924.187 10.312.532 3.388.344 6.72.86 10 1.53a99.75 99.75 0 0 1 18.906 5.844c3.014 1.275 5.956 2.699 8.812 4.25s5.632 3.252 8.313 5.063 5.261 3.727 7.75 5.781 4.876 4.252 7.156 6.531 4.478 4.668 6.531 7.157c2.054 2.488 3.97 5.069 5.781 7.75s3.511 5.456 5.063 8.312 2.975 5.798 4.25 8.813 2.393 6.094 3.375 9.25a99.975 99.975 0 0 1 2.469 9.656c.671 3.28 1.187 6.612 1.531 10 .344 3.388.531 6.834.531 10.313 0 3.479-.187 6.924-.531 10.312-.196 1.927-.572 3.794-.875 5.687h29.562c.112-.914.313-1.799.406-2.718.444-4.37.656-8.794.656-13.281 0-4.488-.212-8.911-.656-13.281-.444-4.37-1.134-8.675-2-12.906a129.23 129.23 0 0 0-3.187-12.47c-1.266-4.07-2.73-8.049-4.375-11.937a129.601 129.601 0 0 0-11.969-22.094c-2.336-3.458-4.85-6.79-7.5-10s-5.434-6.31-8.375-9.25-6.04-5.726-9.25-8.375-6.542-5.164-10-7.5a129.885 129.885 0 0 0-22.094-11.97 128.88 128.88 0 0 0-11.938-4.374 129.205 129.205 0 0 0-12.469-3.188c-4.231-.866-8.536-1.556-12.906-2-4.349-.48-8.773-.69-13.26-.69z" style="fill-opacity:.89686;fill:#b3b3b3"/></g></svg>
202\ No newline at end of file
203 diff --git a/public/img/minimal-favicon.png b/public/img/minimal-favicon.png
204new file mode 100644
205index 0000000..e7cdbde
206 Binary files /dev/null and b/public/img/minimal-favicon.png differ
207 diff --git a/public/img/minimal-minimal.png b/public/img/minimal-minimal.png
208new file mode 100644
209index 0000000..8f60a03
210 Binary files /dev/null and b/public/img/minimal-minimal.png differ
211 diff --git a/public/img/minimal-thumbnail.png b/public/img/minimal-thumbnail.png
212new file mode 100644
213index 0000000..e6c0478
214 Binary files /dev/null and b/public/img/minimal-thumbnail.png differ
215 diff --git a/public/img/sourcehut.svg b/public/img/sourcehut.svg
216new file mode 100644
217index 0000000..c7a12e9
218--- /dev/null
219+++ b/public/img/sourcehut.svg
220 @@ -0,0 +1,77 @@
221+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
222+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
223+
224+ <svg
225+ width="300"
226+ height="80"
227+ viewBox="0 0 79.375001 21.166666"
228+ version="1.1"
229+ id="svg5"
230+ inkscape:version="1.1 (c68e22c387, 2021-05-23)"
231+ sodipodi:docname="sourcehut-black.svg"
232+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
233+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
234+ xmlns="http://www.w3.org/2000/svg"
235+ xmlns:svg="http://www.w3.org/2000/svg">
236+ <sodipodi:namedview
237+ id="namedview7"
238+ pagecolor="#505050"
239+ bordercolor="#ffffff"
240+ borderopacity="1"
241+ inkscape:pageshadow="0"
242+ inkscape:pageopacity="0"
243+ inkscape:pagecheckerboard="1"
244+ inkscape:document-units="px"
245+ showgrid="false"
246+ units="px"
247+ height="80px"
248+ inkscape:zoom="3.0930415"
249+ inkscape:cx="152.6006"
250+ inkscape:cy="77.75518"
251+ inkscape:window-width="1920"
252+ inkscape:window-height="1059"
253+ inkscape:window-x="0"
254+ inkscape:window-y="0"
255+ inkscape:window-maximized="1"
256+ inkscape:current-layer="layer1" />
257+ <defs
258+ id="defs2">
259+ <rect
260+ x="72.43578"
261+ y="4.989779"
262+ width="233.3009"
263+ height="93.363552"
264+ id="rect22993" />
265+ <rect
266+ x="76.460692"
267+ y="7.5029808"
268+ width="210.43456"
269+ height="46.880685"
270+ id="rect5098" />
271+ </defs>
272+ <g
273+ inkscape:label="Layer 1"
274+ inkscape:groupmode="layer"
275+ id="layer1">
276+ <path
277+ d="m 10.58314,1.3501934 c -5.1005652,0 -9.23314,4.1325746 -9.23314,9.2331386 0,5.100566 4.1325748,9.233141 9.23314,9.233141 5.100565,0 9.23314,-4.132575 9.23314,-9.233141 0,-5.100564 -4.132575,-9.2331386 -9.23314,-9.2331386 z m 0,16.6792216 c -4.1139596,0 -7.4460807,-3.332121 -7.4460807,-7.446083 0,-4.1139582 3.3321211,-7.4460793 7.4460807,-7.4460793 4.11396,0 7.446082,3.3321211 7.446082,7.4460793 0,4.113962 -3.332122,7.446083 -7.446082,7.446083 z"
278+ id="path49"
279+ style="stroke-width:0.0372303" />
280+ <text
281+ xml:space="preserve"
282+ transform="scale(0.26458333)"
283+ id="text22991"
284+ style="fill:black;fill-opacity:1;line-height:1.25;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;white-space:pre;shape-inside:url(#rect22993)" />
285+ <text
286+ xml:space="preserve"
287+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.2889px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
288+ x="22.349714"
289+ y="13.896598"
290+ id="text29885"><tspan
291+ sodipodi:role="line"
292+ id="tspan29883"
293+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.2889px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583"
294+ x="22.349714"
295+ y="13.896598">sourcehut</tspan></text>
296+ </g>
297+ </svg>
298 diff --git a/public/index.html b/public/index.html
299new file mode 100644
300index 0000000..166eabe
301--- /dev/null
302+++ b/public/index.html
303 @@ -0,0 +1,85 @@
304+ <!DOCTYPE html>
305+ <html lang="en-US">
306+ <head>
307+ <meta charset="UTF-8" />
308+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
309+ <meta name="viewport" content="width=device-width, initial-scale=1" />
310+ <title></title>
311+ <meta
312+ name="description"
313+ itemprop="about"
314+ content="ForgeFeed Specifications"
315+ />
316+ <link rel="stylesheet" href="https://forge-feed.org/style.css" />
317+ </head>
318+
319+ <body>
320+ <div class="wrapper">
321+ <header>
322+ <a href="https://forge-feed.org">
323+ <img
324+ src="https://forge-feed.org/img/logo.svg"
325+ class="site-logo"
326+ alt="Logo"
327+ width="275px"
328+ height="auto"
329+ /></a>
330+
331+ <p>ForgeFeed Specifications</p>
332+
333+
334+ <a href="https://codeberg.org/ayllu/forge-feed">
335+ <img
336+ src="https://forge-feed.org/img/codeberg.svg"
337+ class="site-logo"
338+ alt="Codeberg Logo"
339+ />
340+ </a>
341+
342+ <a href="https://git.sr.ht/~kevinschoon/forge-feed">
343+ <img
344+ src="https://forge-feed.org/img/sourcehut.svg"
345+ class="site-logo sourcehut"
346+ alt="Sourcehut Logo"
347+ />
348+ </a>
349+
350+
351+
352+ </header>
353+
354+ <section>
355+ <h1>Forge-Feed</h1>
356+ <p><strong>Please note that these specifications are currently PROVISIONAL
357+ and should not be implemented. This specification is not endorsed or
358+ supported by any particular organizational entity.</strong></p>
359+ <p>ForgeFeed is a collection of specifications and recommendations which when
360+ implemented can enhance interoperability and content discovery of different
361+ <a href="https://en.wikipedia.org/wiki/Forge_(software)">code forges</a>
362+ across the internet. You can validate an existing ForgeFeed implementation
363+ by navigating to the <a href="/validator">validator</a> page.</p>
364+ <p>NOTE that all specifications provided below MAY be implemented indepedenantly
365+ at the descrescion of the developer however it is a recommended that a server
366+ implement them all for maximum effect.</p>
367+ <h1 id="specifications">Specifications</h1>
368+ <p>The following specifications are currently covered:</p>
369+ <ul>
370+ <li><a href="/webfinger-project">WebFinger Project Identification</a> - Identify Software Projects via WebFinger</li>
371+ <li><a href="/webfinger-repository">WebFinger Repository Identification</a> - Identify VCS Repository Content via WebFinger</li>
372+ <li><a href="/project-atom-feed">Atom Feed Project Discovery</a> - Subscribe to a forge over Atom</li>
373+ <li><a href="/meta-tag">ForgeFeed Site Identifier</a> - HTML Meta Tag Indicating ForgeFeed Capabilities</li>
374+ </ul>
375+
376+ </section>
377+
378+ <footer>
379+ <a href="/LICENSE.txt">
380+ <img
381+ src="https://forge-feed.org/img/cc0.png"
382+ alt="cc0 logo"
383+ />
384+ </a>
385+ </footer>
386+ </div>
387+ </body>
388+ </html>
389 diff --git a/public/meta-tag/index.html b/public/meta-tag/index.html
390new file mode 100644
391index 0000000..f0a99fd
392--- /dev/null
393+++ b/public/meta-tag/index.html
394 @@ -0,0 +1,84 @@
395+ <!DOCTYPE html>
396+ <html lang="en-US">
397+ <head>
398+ <meta charset="UTF-8" />
399+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
400+ <meta name="viewport" content="width=device-width, initial-scale=1" />
401+ <title></title>
402+ <meta
403+ name="description"
404+ itemprop="about"
405+ content="ForgeFeed Specifications"
406+ />
407+ <link rel="stylesheet" href="https://forge-feed.org/style.css" />
408+ </head>
409+
410+ <body>
411+ <div class="wrapper">
412+ <header>
413+ <a href="https://forge-feed.org">
414+ <img
415+ src="https://forge-feed.org/img/logo.svg"
416+ class="site-logo"
417+ alt="Logo"
418+ width="275px"
419+ height="auto"
420+ /></a>
421+
422+ <p>ForgeFeed Specifications</p>
423+
424+
425+ <a href="https://codeberg.org/ayllu/forge-feed">
426+ <img
427+ src="https://forge-feed.org/img/codeberg.svg"
428+ class="site-logo"
429+ alt="Codeberg Logo"
430+ />
431+ </a>
432+
433+ <a href="https://git.sr.ht/~kevinschoon/forge-feed">
434+ <img
435+ src="https://forge-feed.org/img/sourcehut.svg"
436+ class="site-logo sourcehut"
437+ alt="Sourcehut Logo"
438+ />
439+ </a>
440+
441+
442+
443+ </header>
444+
445+ <section>
446+ <h1>ForgeFeed Identifier</h1>
447+ <p>The ForgeFeed identifier is a simple HTML <a href="https://html.spec.whatwg.org/multipage/semantics.html#the-meta-element">meta tag</a>
448+ which provides a reference to a particular forge-feed enabled project.
449+ Including a <code>forge-feed://project</code> or <code>forge-feed://repository</code> tag in a given HTML page
450+ provides a reference to a codebase or project page which can be used to construct
451+ a webfinger query. Using the forge-feed identifier indicates that a given HTML
452+ page is associated with a certain software project.</p>
453+ <h2 id="tags">Tags</h2>
454+ <h3 id="forge-feed">forge-feed</h3>
455+ <p>The content of a forge-feed:project tag MUST contain a valid <a href="/webfinger-project">project-uri</a>
456+ or <a href="/webfinger-repository">repository-uri</a>. If a hostname is not part of the project-uri
457+ then the host is assumed to be at the domain which is serving the content.</p>
458+ <pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#65737e;">&lt;!-- Project on the same host --&gt;
459+ </span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">forge-feed</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">project://spartacus</span><span>&quot;/&gt;
460+ </span><span style="color:#65737e;">&lt;!-- Or another host --&gt;
461+ </span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">forge-feed</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">project://spartacus@example.org</span><span>&quot;/&gt;
462+ </span><span style="color:#65737e;">&lt;!-- Or a repository link --&gt;
463+ </span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">forge-feed</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">repository://spartacus@code.example.org</span><span>&quot;/&gt;
464+ </span></code></pre>
465+
466+ </section>
467+
468+ <footer>
469+ <a href="/LICENSE.txt">
470+ <img
471+ src="https://forge-feed.org/img/cc0.png"
472+ alt="cc0 logo"
473+ />
474+ </a>
475+ </footer>
476+ </div>
477+ </body>
478+ </html>
479 diff --git a/public/project-atom-feed/index.html b/public/project-atom-feed/index.html
480new file mode 100644
481index 0000000..2545c57
482--- /dev/null
483+++ b/public/project-atom-feed/index.html
484 @@ -0,0 +1,179 @@
485+ <!DOCTYPE html>
486+ <html lang="en-US">
487+ <head>
488+ <meta charset="UTF-8" />
489+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
490+ <meta name="viewport" content="width=device-width, initial-scale=1" />
491+ <title></title>
492+ <meta
493+ name="description"
494+ itemprop="about"
495+ content="ForgeFeed Specifications"
496+ />
497+ <link rel="stylesheet" href="https://forge-feed.org/style.css" />
498+ </head>
499+
500+ <body>
501+ <div class="wrapper">
502+ <header>
503+ <a href="https://forge-feed.org">
504+ <img
505+ src="https://forge-feed.org/img/logo.svg"
506+ class="site-logo"
507+ alt="Logo"
508+ width="275px"
509+ height="auto"
510+ /></a>
511+
512+ <p>ForgeFeed Specifications</p>
513+
514+
515+ <a href="https://codeberg.org/ayllu/forge-feed">
516+ <img
517+ src="https://forge-feed.org/img/codeberg.svg"
518+ class="site-logo"
519+ alt="Codeberg Logo"
520+ />
521+ </a>
522+
523+ <a href="https://git.sr.ht/~kevinschoon/forge-feed">
524+ <img
525+ src="https://forge-feed.org/img/sourcehut.svg"
526+ class="site-logo sourcehut"
527+ alt="Sourcehut Logo"
528+ />
529+ </a>
530+
531+
532+
533+ </header>
534+
535+ <section>
536+ <h1>Atom Feed</h1>
537+ <h1 id="atom-feeds">Atom Feeds</h1>
538+ <p>A forge SHOULD expose an <a href="https://www.rfc-editor.org/rfc/rfc4287">Atom</a>
539+ feed in order for other peers to subscribe to interesting code projects that
540+ are developed on your forge. The server SHOULD expose projects ordered by those
541+ which have been recently updated. The heuristic used to determine what has
542+ been updated depends on the implementor forge. A common way to do this may be
543+ to simply look at your VCS's concept of a commit and order updates with that.</p>
544+ <h2 id="determine-if-a-host-supports-forge-feed">Determine if a Host Supports Forge Feed</h2>
545+ <p>In order to participate as an Atom enabled ForgeFeed the forge MUST present an
546+ HTML link element such as below at the root domain of your forge. For example,
547+ <code>code.example.org</code> MUST have a link element present in it's html header:</p>
548+ <pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">html</span><span>&gt;
549+ </span><span> &lt;</span><span style="color:#bf616a;">head</span><span>&gt;
550+ </span><span> &lt;</span><span style="color:#bf616a;">title</span><span>&gt;My Forge&lt;/</span><span style="color:#bf616a;">title</span><span>&gt;
551+ </span><span> &lt;</span><span style="color:#bf616a;">link
552+ </span><span> </span><span style="color:#d08770;">rel</span><span>=&quot;</span><span style="color:#a3be8c;">alternate</span><span>&quot;
553+ </span><span> </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">application/atom+xml</span><span>&quot;
554+ </span><span> </span><span style="color:#d08770;">title</span><span>=&quot;</span><span style="color:#a3be8c;">Recent Forge Activity</span><span>&quot;
555+ </span><span> </span><span style="color:#d08770;">href</span><span>=&quot;</span><span style="color:#a3be8c;">https://code.example.org/firehose.xml</span><span>&quot; /&gt;
556+ </span><span> </span><span style="color:#65737e;">&lt;!-- index-url refers to the RSS link on the current webpage which contains forge updates --&gt;
557+ </span><span> &lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">forge-feed:index-url</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">https://code.example.org/firehose.xml</span><span>&quot;/&gt;
558+ </span><span> &lt;/</span><span style="color:#bf616a;">head</span><span>&gt;
559+ </span><span> &lt;</span><span style="color:#bf616a;">body</span><span>&gt;
560+ </span><span> ...
561+ </span><span> &lt;/</span><span style="color:#bf616a;">body</span><span>&gt;
562+ </span><span>&lt;/</span><span style="color:#bf616a;">html</span><span>&gt;
563+ </span></code></pre>
564+ <p>The link MAY have a title of "Recent Forge Activity" such that it uniquely
565+ identifies this feed as being related to forge updates. A server MUST provide a
566+ meta tag with the name forge-feed:index where the content of the tag matches
567+ the href of an Atom link listed on this page which should be used for indexing
568+ by external crawlers. Applications which rely on the forge-feed:index meta
569+ tag MUST stop crawling the serving website if this tag is no longer present
570+ in the pages HTML.</p>
571+ <h3 id="an-example-feed">An example Feed</h3>
572+ <p>Below is an example Atom feed with an optional repository section (described below).</p>
573+ <pre data-lang="xml" style="background-color:#2b303b;color:#c0c5ce;" class="language-xml "><code class="language-xml" data-lang="xml"><span>&lt;?</span><span style="color:#bf616a;">xml </span><span style="color:#d08770;">version</span><span>=&quot;</span><span style="color:#a3be8c;">1.0</span><span>&quot; </span><span style="color:#d08770;">encoding</span><span>=&quot;</span><span style="color:#a3be8c;">utf-8</span><span>&quot;?&gt;
574+ </span><span> &lt;</span><span style="color:#bf616a;">feed </span><span style="color:#d08770;">xmlns</span><span>=&quot;</span><span style="color:#a3be8c;">http://www.w3.org/2005/Atom</span><span>&quot;
575+ </span><span> </span><span style="color:#d08770;">xmlns:forge-feed</span><span>=&quot;</span><span style="color:#a3be8c;">http://forge-feed.org/project-atom-feed</span><span>&quot;&gt;
576+ </span><span>
577+ </span><span> &lt;</span><span style="color:#bf616a;">title</span><span>&gt;Acme Forge Firehose&lt;/</span><span style="color:#bf616a;">title</span><span>&gt;
578+ </span><span> &lt;</span><span style="color:#bf616a;">subtitle</span><span>&gt;Recent Project Updates @ Acme Forge&lt;/</span><span style="color:#bf616a;">subtitle</span><span>&gt;
579+ </span><span> &lt;</span><span style="color:#bf616a;">link </span><span style="color:#d08770;">href</span><span>=&quot;</span><span style="color:#a3be8c;">https://code.example.org/firehose.xml</span><span>&quot; </span><span style="color:#d08770;">rel</span><span>=&quot;</span><span style="color:#a3be8c;">self</span><span>&quot; /&gt;
580+ </span><span> &lt;</span><span style="color:#bf616a;">link </span><span style="color:#d08770;">href</span><span>=&quot;</span><span style="color:#a3be8c;">https://code.example.org/</span><span>&quot; /&gt;
581+ </span><span> &lt;</span><span style="color:#bf616a;">id</span><span>&gt;urn:uuid:44decbfc-ec17-42fa-994e-67dc13029072&lt;/</span><span style="color:#bf616a;">id</span><span>&gt;
582+ </span><span> &lt;</span><span style="color:#bf616a;">updated</span><span>&gt;2025-06-18T11:23:02Z&lt;/</span><span style="color:#bf616a;">updated</span><span>&gt;
583+ </span><span>
584+ </span><span> &lt;</span><span style="color:#bf616a;">entry</span><span>&gt;
585+ </span><span> &lt;</span><span style="color:#bf616a;">title</span><span>&gt;Spartacus Game&lt;/</span><span style="color:#bf616a;">title</span><span>&gt;
586+ </span><span> &lt;</span><span style="color:#bf616a;">link </span><span style="color:#d08770;">href</span><span>=&quot;</span><span style="color:#a3be8c;">https://code.example.org/spartacus</span><span>&quot; /&gt;
587+ </span><span> &lt;</span><span style="color:#bf616a;">id</span><span>&gt;urn:uuid:51b2ad2b-34ff-4a69-9f45-a300bf296d05&lt;/</span><span style="color:#bf616a;">id</span><span>&gt;
588+ </span><span> &lt;</span><span style="color:#bf616a;">published</span><span>&gt;2025-06-18T11:23:02Z&lt;/</span><span style="color:#bf616a;">published</span><span>&gt;
589+ </span><span> &lt;</span><span style="color:#bf616a;">updated</span><span>&gt;2025-06-18T11:23:02Z&lt;/</span><span style="color:#bf616a;">updated</span><span>&gt;
590+ </span><span> &lt;</span><span style="color:#bf616a;">summary</span><span>&gt;A Game Engine </span><span style="background-color:#bf616a;color:#2b303b;">&amp;</span><span> Text Adventure Written in FORTRAN 77&lt;/</span><span style="color:#bf616a;">summary</span><span>&gt;
591+ </span><span> &lt;</span><span style="color:#bf616a;">forge-feed:project</span><span>&gt;project:spartacus&lt;/</span><span style="color:#bf616a;">forge-feed:project</span><span>&gt;
592+ </span><span> &lt;/</span><span style="color:#bf616a;">entry</span><span>&gt;
593+ </span><span>&lt;/</span><span style="color:#bf616a;">feed</span><span>&gt;
594+ </span></code></pre>
595+ <h3 id="forgefeed-extension">ForgeFeed Extension</h3>
596+ <p>In order to facilitate discovery by external indexes the server SHOULD implement
597+ implement the webfinger <a href="/webfinger-project">project specification</a> as well as
598+ the <a href="/webfinger-repository">repository specification</a> so that repository indexes
599+ may populate their state with rich information about code repositories hosted
600+ on your server.</p>
601+ <p>The extension currently supports only a single field called <code>project</code>.</p>
602+ <pre data-lang="xml" style="background-color:#2b303b;color:#c0c5ce;" class="language-xml "><code class="language-xml" data-lang="xml"><span>&lt;</span><span style="color:#bf616a;">forge-feed:project</span><span>&gt;project:spartacus&lt;/</span><span style="color:#bf616a;">forge-feed:project</span><span>&gt;
603+ </span></code></pre>
604+ <p>If the host section of the URI is included that is MUST match domain name
605+ of the server which is providing the feed.</p>
606+ <h3 id="security-concerns">Security Concerns</h3>
607+ <h4 id="private-projects">Private Projects</h4>
608+ <p>Forge-feed enabled Atom feeds have no support for sharing private projects
609+ and any project that is not shared publicly on the internet must be hidden
610+ from the Atom activity feed stream. If your forge provides the ability to change
611+ a project from public to private it should be understood that clients may
612+ already have cached versions of your project data.</p>
613+ <h3 id="recommendations-for-enumerating-project-events">Recommendations for Enumerating Project Events</h3>
614+ <h4 id="specify-a-maximum-timeframe">Specify a Maximum Timeframe</h4>
615+ <p>Your activity feed should not include project events that are older than 1
616+ week.</p>
617+ <h4 id="project-items-should-be-unique">Project Items SHOULD be Unique</h4>
618+ <p>Although it is permitted to return duplicate projects the feed SHOULD only
619+ return unique projects in a given window.</p>
620+ <h4 id="event-clamping">Event "Clamping"</h4>
621+ <p>It may be undesirable to enumerate project events items with the simple
622+ heuristic of</p>
623+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>end = time.now()
624+ </span><span>start = end - 6h
625+ </span><span>
626+ </span><span>events = project_events_between(start, end)
627+ </span></code></pre>
628+ <p>Because the oldest project events will fall out of the time window on
629+ subsequent queries by feed readers. This can cause some feed readers to
630+ frequently request new content from your server. Instead events can be
631+ "clamped" within a certain time period. For example you may choose to
632+ publish four buckets of updates per day:</p>
633+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>00:00:00 06:00:00
634+ </span><span>06:00:00 12:00:00
635+ </span><span>12:00:00 18:00:00
636+ </span><span>18:00:00 00:00:00
637+ </span><span>
638+ </span><span>For example if the current time is 2021-03-05 13:00:55 UTC you could use
639+ </span><span>the following example to return a summary of project events from the
640+ </span><span>previous time bucket.
641+ </span><span>
642+ </span><span>now = time.now()
643+ </span><span>start = now.set_time(&quot;06:00:00&quot;)
644+ </span><span>end = now.set_time(&quot;12:00:00&quot;)
645+ </span><span>
646+ </span><span>events = project_events_between(start, end)
647+ </span></code></pre>
648+ <p>This approach with a TTL value of 60 (minutes) will reduce excess requests
649+ from some readers but allow all clients to receive timely updates.</p>
650+
651+ </section>
652+
653+ <footer>
654+ <a href="/LICENSE.txt">
655+ <img
656+ src="https://forge-feed.org/img/cc0.png"
657+ alt="cc0 logo"
658+ />
659+ </a>
660+ </footer>
661+ </div>
662+ </body>
663+ </html>
664 diff --git a/public/robots.txt b/public/robots.txt
665new file mode 100644
666index 0000000..a2e9da9
667--- /dev/null
668+++ b/public/robots.txt
669 @@ -0,0 +1,4 @@
670+ User-agent: *
671+ Disallow:
672+ Allow: /
673+ Sitemap: https://forge-feed.org/sitemap.xml
674 diff --git a/public/site.webmanifest b/public/site.webmanifest
675new file mode 100644
676index 0000000..06d341a
677--- /dev/null
678+++ b/public/site.webmanifest
679 @@ -0,0 +1,19 @@
680+ {
681+ "name": "",
682+ "short_name": "",
683+ "icons": [
684+ {
685+ "src": "/android-chrome-192x192.png",
686+ "sizes": "192x192",
687+ "type": "image/png"
688+ },
689+ {
690+ "src": "/android-chrome-512x512.png",
691+ "sizes": "512x512",
692+ "type": "image/png"
693+ }
694+ ],
695+ "theme_color": "#ffffff",
696+ "background_color": "#ffffff",
697+ "display": "standalone"
698+ }
699 diff --git a/public/sitemap.xml b/public/sitemap.xml
700new file mode 100644
701index 0000000..355efaf
702--- /dev/null
703+++ b/public/sitemap.xml
704 @@ -0,0 +1,21 @@
705+ <?xml version="1.0" encoding="UTF-8"?>
706+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
707+ <url>
708+ <loc>https://forge-feed.org/</loc>
709+ </url>
710+ <url>
711+ <loc>https://forge-feed.org/meta-tag/</loc>
712+ </url>
713+ <url>
714+ <loc>https://forge-feed.org/project-atom-feed/</loc>
715+ </url>
716+ <url>
717+ <loc>https://forge-feed.org/validator/</loc>
718+ </url>
719+ <url>
720+ <loc>https://forge-feed.org/webfinger-project/</loc>
721+ </url>
722+ <url>
723+ <loc>https://forge-feed.org/webfinger-repository/</loc>
724+ </url>
725+ </urlset>
726 diff --git a/public/style.css b/public/style.css
727new file mode 100644
728index 0000000..5daa2e9
729--- /dev/null
730+++ b/public/style.css
731 @@ -0,0 +1,275 @@
732+ .wrapper {
733+ width: 860px;
734+ margin: 0 auto;
735+ }
736+
737+ .positive {
738+ background-color: green;
739+ color: white;
740+ font-weight: bolder;
741+ }
742+
743+ .negative {
744+ background-color: salmon;
745+ color: white;
746+ font-weight: bolder;
747+ }
748+
749+ #results {
750+ overflow-x: scroll;
751+ }
752+
753+ header {
754+ width: 170px;
755+ float: left;
756+ position: fixed;
757+ -webkit-font-smoothing: subpixel-antialiased;
758+ }
759+
760+ body {
761+ background-color: #fff;
762+ padding: 50px;
763+ color: #222;
764+ font-size: 1.2em;
765+ font-family: monospace;
766+ font-weight: 400;
767+ }
768+
769+ h1,
770+ h2,
771+ h3,
772+ h4,
773+ h5,
774+ h6 {
775+ color: #222;
776+ margin: 0 0 20px;
777+ }
778+
779+ p,
780+ ul,
781+ ol,
782+ table,
783+ pre,
784+ dl {
785+ margin: 0 0 20px;
786+ }
787+
788+ h1,
789+ h2,
790+ h3 {
791+ line-height: 1.1;
792+ }
793+
794+ h1 {
795+ font-size: 28px;
796+ }
797+
798+ a {
799+ color: #267cb9;
800+ text-decoration: none;
801+ }
802+
803+ a:hover,
804+ a:focus {
805+ color: salmon;
806+ }
807+
808+ a small {
809+ font-size: 11px;
810+ color: black;
811+ margin-top: -0.3em;
812+ display: block;
813+ }
814+
815+ a:hover small {
816+ color: #777;
817+ }
818+
819+ strong {
820+ color: #222;
821+ font-weight: 700;
822+ }
823+
824+ section {
825+ width: 600px;
826+ float: right;
827+ padding-bottom: 50px;
828+ }
829+
830+ small {
831+ font-size: 11px;
832+ }
833+
834+ hr {
835+ border: 0;
836+ background: #e5e5e5;
837+ height: 1px;
838+ margin: 0 0 20px;
839+ }
840+
841+ footer {
842+ width: 270px;
843+ float: left;
844+ position: fixed;
845+ bottom: 50px;
846+ -webkit-font-smoothing: subpixel-antialiased;
847+ }
848+
849+ pre {
850+ padding: 8px 15px;
851+ border-radius: 5px;
852+ border: 1px solid #e5e5e5;
853+ overflow-x: auto;
854+ }
855+
856+ .site-logo {
857+ margin-bottom: 1rem;
858+ width: 100%;
859+ }pre,
860+ code {
861+ font-family: monospace;
862+ }
863+
864+ code {
865+ border-radius: 3px;
866+ }
867+
868+ pre {
869+ overflow: auto;
870+ }
871+
872+ pre code {
873+ background-color: transparent;
874+ color: inherit;
875+ }
876+
877+ @media (prefers-color-scheme: dark) {
878+
879+ .sourcehut {
880+ filter: invert(100%);
881+ }
882+
883+ body {
884+ background-color: #222;
885+ color: #FFF
886+ }
887+
888+ strong {
889+ color: #FFF;
890+ }
891+
892+ h1,
893+ h2,
894+ h3,
895+ h4,
896+ h5,
897+ h6 {
898+ color: #FFF;
899+ }
900+
901+ a small {
902+ color: white;
903+ }
904+ }
905+
906+ footer>a>img {
907+ max-height: 20px;
908+ }
909+
910+ div#validator > form {
911+ display: flex;
912+ flex-direction: column;
913+ background-color: grey;
914+ border: solid;
915+ padding: 10px;
916+ }
917+
918+ div#validator > form > input {
919+ margin: 2px;
920+ }
921+
922+ div#validator > form > button {
923+ margin-top: 20px;
924+ }
925+
926+ .computed {
927+ padding: 8px 15px;
928+ border-radius: 5px;
929+ border: 1px solid #e5e5e5;
930+ }
931+
932+ @media print,
933+ screen and (max-width: 960px) {
934+ div.wrapper {
935+ width: auto;
936+ margin: 0;
937+ }
938+
939+ header,
940+ section,
941+ footer {
942+ float: none;
943+ position: static;
944+ width: auto;
945+ }
946+
947+ header {
948+ padding-right: 320px;
949+ }
950+
951+ section {
952+ border: 1px solid #e5e5e5;
953+ border-width: 1px 0;
954+ padding: 20px 0;
955+ margin: 0 0 20px;
956+ }
957+
958+ header a small {
959+ display: inline;
960+ }
961+
962+ header ul {
963+ position: absolute;
964+ right: 50px;
965+ top: 52px;
966+ }
967+ }
968+
969+ @media print,
970+ screen and (max-width: 720px) {
971+ body {
972+ word-wrap: break-word;
973+ }
974+
975+ header {
976+ padding: 0;
977+ }
978+
979+ header ul,
980+ header p.view {
981+ position: static;
982+ margin: 1rem auto;
983+ }
984+
985+ pre,
986+ code {
987+ word-wrap: normal;
988+ text-wrap: wrap;
989+ }
990+ }
991+
992+ @media print,
993+ screen and (max-width: 480px) {
994+ body {
995+ padding: 15px;
996+ }
997+ }
998+
999+ @media print {
1000+ body {
1001+ padding: 0.4in;
1002+ font-size: 12pt;
1003+ color: #444;
1004+ }
1005+ }
1006+
1007 diff --git a/public/validator/index.html b/public/validator/index.html
1008new file mode 100644
1009index 0000000..aeb8ec7
1010--- /dev/null
1011+++ b/public/validator/index.html
1012 @@ -0,0 +1,104 @@
1013+ <!DOCTYPE html>
1014+ <html lang="en-US">
1015+ <head>
1016+ <meta charset="UTF-8" />
1017+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
1018+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1019+ <title></title>
1020+ <meta
1021+ name="description"
1022+ itemprop="about"
1023+ content="ForgeFeed Specifications"
1024+ />
1025+ <link rel="stylesheet" href="https://forge-feed.org/style.css" />
1026+ </head>
1027+
1028+ <body>
1029+ <div class="wrapper">
1030+ <header>
1031+ <a href="https://forge-feed.org">
1032+ <img
1033+ src="https://forge-feed.org/img/logo.svg"
1034+ class="site-logo"
1035+ alt="Logo"
1036+ width="275px"
1037+ height="auto"
1038+ /></a>
1039+
1040+ <p>ForgeFeed Specifications</p>
1041+
1042+
1043+ <a href="https://codeberg.org/ayllu/forge-feed">
1044+ <img
1045+ src="https://forge-feed.org/img/codeberg.svg"
1046+ class="site-logo"
1047+ alt="Codeberg Logo"
1048+ />
1049+ </a>
1050+
1051+ <a href="https://git.sr.ht/~kevinschoon/forge-feed">
1052+ <img
1053+ src="https://forge-feed.org/img/sourcehut.svg"
1054+ class="site-logo sourcehut"
1055+ alt="Sourcehut Logo"
1056+ />
1057+ </a>
1058+
1059+
1060+
1061+ </header>
1062+
1063+ <section>
1064+ <h1>Validator</h1>
1065+ <p>
1066+ Fill out the form below to validate a ForgeFeed enabled website. Note that
1067+ your server must have the appropriate CORS headers:
1068+ <div>
1069+ <code>Access-Control-Allow-Origin: https://forge-feed.org</code>.
1070+ </div>
1071+ </p>
1072+ <div id="validator">
1073+ <form id="fqdn">
1074+ <label for="domain">
1075+ Schema + FQDN, e.g. <code>https://ayllu-forge.org</code>
1076+ </label>
1077+ <input
1078+ type="text"
1079+ id="domain"
1080+ name="domain"
1081+ value="ayllu-forge.org"
1082+ required>
1083+ </form>
1084+ <form id="project" onsubmit="return false">
1085+ <label for="project-uri">Project URI</label>
1086+ <input type="text" id="project-uri" name="project-uri" value="project://ayllu" required>
1087+ <button type="submit" onclick="validateProject()">Validate</button>
1088+ </form>
1089+ <form id="repository" onsubmit="return false">
1090+ <label for="repository-uri">Repository URI</label>
1091+ <input type="text" id="repository-uri" name="repository-uri" value="repository://ayllu/ayllu" required>
1092+ <button type="submit" onclick="validateRepository()">Validate</button>
1093+ </form>
1094+ <!-- <form id="feed" onsubmit="return false"> -->
1095+ <!-- <label for="feed">Feed URL</label> -->
1096+ <!-- <input type="text" id="feed-uri" name="feed-uri" value="/firehose.xml" required> -->
1097+ <!-- <button type="submit" onclick="verify()">Validate</button> -->
1098+ <!-- </form> -->
1099+ <div id="query-url" class="computed"></div>
1100+ <div id="results" class="computed"></div>
1101+
1102+ </div>
1103+ <script src="/verifier.js"></script>
1104+ </section>
1105+
1106+ <footer>
1107+ <a href="/LICENSE.txt">
1108+ <img
1109+ src="https://forge-feed.org/img/cc0.png"
1110+ alt="cc0 logo"
1111+ />
1112+ </a>
1113+ </footer>
1114+ </div>
1115+ </body>
1116+ </html>
1117 diff --git a/public/verifier.js b/public/verifier.js
1118new file mode 100644
1119index 0000000..4f4ab75
1120--- /dev/null
1121+++ b/public/verifier.js
1122 @@ -0,0 +1,203 @@
1123+ const Kind = {
1124+ HREF: "href",
1125+ PROPERTIES: "properties",
1126+ TITLES: "titles",
1127+ };
1128+
1129+ function newRecorder() {
1130+ const Recorder = {
1131+ state: [],
1132+ ok(kind, object) {
1133+ this.state.push({ "kind": kind, "object": object, "error": null })
1134+ },
1135+ error(kind, object, message) {
1136+ this.state.push({ "kind": kind, "object": object, "error": message })
1137+ }
1138+ };
1139+ return Recorder
1140+ }
1141+
1142+ // TODO
1143+ function constraint(kind, values) {
1144+ if (kind == Kind.HREF) {
1145+ function check_href(object, recorder) {
1146+ if (!('href' in object)) {
1147+ recorder.error(Kind.HREF, object, "href not in link",)
1148+ }
1149+ if (URL.parse(object["href"]) == null) {
1150+ recorder.error(Kind.HREF, object, "href value is not a parsable URL: " + object["href"]);
1151+ }
1152+ recorder.ok(Kind.HREF, object);
1153+ };
1154+ return { kind: Kind.HREF, check: check_href }
1155+ } else if (kind == Kind.PROPERTIES) {
1156+ function check_properties(object, recorder) {
1157+ if (!('properties' in object)) {
1158+ recorder.error(Kind.PROPERTIES, object, "properties not present in link");
1159+ };
1160+ let properties = object['properties'];
1161+ let keys = Object.keys(values);
1162+ for (let i = 0; i < values.length; i++) {
1163+ let name = keys[i];
1164+ if (!(name in properties)) {
1165+ recorder.error(Kind.PROPERTIES, object, "Missing property: " + name);
1166+ };
1167+ // TODO check values
1168+ }
1169+ recorder.ok(Kind.PROPERTIES, object);
1170+ return null;
1171+ };
1172+ return { kind: Kind.PROPERTIES, check: check_properties }
1173+ } else if (kind == Kind.TITLES) {
1174+ function check_titles(object, recorder) {
1175+ if (!('titles' in object)) {
1176+ recorder.error(Kind.TITLES, object, "titles not present in object");
1177+ };
1178+ recorder.ok(Kind.TITLES, object);
1179+ };
1180+ return { kind: Kind.TITLES, check: check_titles }
1181+ }
1182+ }
1183+
1184+ function validateLink(object, recorder, constraints = []) {
1185+ for (let i = 0; i < constraints.length; i++) {
1186+ let constraint = constraints[i];
1187+ constraint.check(object, recorder);
1188+ }
1189+ }
1190+
1191+ const _vcs_types = ["bzr", "darcs", "fossil", "git", "hg", "pijul", "svn"];
1192+
1193+ const _project = {
1194+ "http://forge-feed.org/rel/avatar": [constraint(Kind.HREF)],
1195+ "http://forge-feed.org/rel/chatroom": [
1196+ constraint(Kind.HREF),
1197+ constraint(Kind.PROPERTIES, {
1198+ "http://forge-feed.org/ns/chatroom": ["irc", "xmpp"]
1199+ })
1200+ ],
1201+ "http://forge-feed.org/rel/description": [constraint(Kind.TITLES)],
1202+ "http://forge-feed.org/rel/label": [
1203+ constraint(Kind.PROPERTIES, { "http://forge-feed.org/ns/label": null }),
1204+ ],
1205+ "http://forge-feed.org/rel/homepage": [constraint(Kind.HREF)],
1206+ "http://forge-feed.org/rel/repository": [
1207+ constraint(Kind.HREF),
1208+ constraint(Kind.PROPERTIES, {
1209+ "http://forge-feed.org/ns/repository-uri": null,
1210+ "http://forge-feed.org/ns/vcs-type": _vcs_types,
1211+ }),
1212+ constraint(Kind.TITLES),
1213+ ],
1214+ "http://forge-feed.org/rel/mailing-list": [
1215+ constraint(Kind.HREF),
1216+ constraint(Kind.PROPERTIES, {
1217+ "http://feed-forge.org/ns/mailing-list-subscribe": null,
1218+ "http://feed-forge.org/ns/mailing-list-unsubscribe": null,
1219+ })
1220+ ],
1221+ "http://forge-feed.org/rel/ticketing-system": [constraint(Kind.HREF)],
1222+ };
1223+
1224+ const _repository = {
1225+ "http://forge-feed.org/rel/avatar": [constraint(Kind.HREF)],
1226+ "http://feed-forge.org/rel/clone": [
1227+ constraint(Kind.HREF),
1228+ constraint(Kind.PROPERTIES, {
1229+ "http://feed-forge.org/ns/vcs-type": _vcs_types,
1230+ })],
1231+ "http://forge-feed.org/rel/description": [constraint(Kind.TITLES)],
1232+ "http://forge-feed.org/rel/license": [constraint(Kind.PROPERTIES, {
1233+ "http://forge-feed.org/ns/spdx-identifier": null
1234+ })],
1235+ "http://forge-feed.org/rel/label": [
1236+ constraint(Kind.PROPERTIES, { "http://forge-feed.org/ns/label": null }),
1237+ ],
1238+ "http://forge-feed.org/rel/homepage": [constraint(Kind.HREF)],
1239+ "http://forge-feed.org/rel/project": [
1240+ constraint(Kind.HREF),
1241+ constraint(Kind.PROPERTIES, { "http://forge-feed.org/ns/vcs-type": _vcs_types }),
1242+ ],
1243+ "http://forge-feed.org/rel/mailing-list": [
1244+ constraint(Kind.HREF),
1245+ constraint(Kind.PROPERTIES, {
1246+ "http://feed-forge.org/ns/mailing-list-subscribe": null,
1247+ "http://feed-forge.org/ns/mailing-list-unsubscribe": null,
1248+ })
1249+ ],
1250+ "http://forge-feed.org/rel/ticketing-system": [constraint(Kind.HREF)],
1251+ };
1252+
1253+ function makeTable(data) {
1254+ let innerHTML = "<table id=\"test-results\"><tr><th>Link</th><th>State</th><th>Message</th><th>Object</th><tr>";
1255+ for (let i = 0; i < data.length; i++) {
1256+ let result = data[i];
1257+ if (result.error) {
1258+ let relationship = result.object["rel"];
1259+ innerHTML += "<tr class=\"negative\">" + "<td>" + relationship + "</td>" +
1260+ "<td>Fail</td><td>" + result.message + "</td><td><code>" +
1261+ JSON.stringify(result.object) + "</code></td></tr>";
1262+ } else {
1263+ let relationship = result.object["rel"];
1264+ innerHTML += "<tr class=\"positive\"><td>" + relationship + "</td>" +
1265+ "<td>Pass</td><td></td><td>" + JSON.stringify(result.object) +
1266+ "</td></tr>";
1267+ }
1268+ }
1269+ innerHTML += "</table>";
1270+ return innerHTML
1271+ }
1272+
1273+ function doQuery(url, cb) {
1274+ let xhr = new XMLHttpRequest();
1275+ xhr.open('GET', url, true);
1276+ xhr.onload = function() {
1277+ if (xhr.status === 200) {
1278+ cb(xhr.responseText)
1279+ }
1280+ };
1281+ xhr.send();
1282+ }
1283+
1284+ function validateProject() {
1285+ document.getElementById("results").innerText = "";
1286+ const fqdn = document.getElementById("fqdn")[0].value;
1287+ const project = document.getElementById("project")[0].value;
1288+ let queryUrl = fqdn + "/.well-known/webfinger?resource=" + project;
1289+ document.getElementById("query-url").innerHTML = "<code>" + queryUrl + "</code>";
1290+ doQuery(queryUrl, function(data) {
1291+ let parsed = JSON.parse(data);
1292+ let recorder = newRecorder();
1293+ for (let i = 0; i < parsed.links.length; i++) {
1294+ let link = parsed.links[i];
1295+ if (link["rel"] in _project) {
1296+ let constraints = _project[link["rel"]];
1297+ validateLink(link, recorder, constraints);
1298+ }
1299+ };
1300+ let innerHTML = makeTable(recorder.state);
1301+ document.getElementById("results").innerHTML = innerHTML;
1302+ });
1303+ }
1304+
1305+ function validateRepository() {
1306+ document.getElementById("results").innerText = "";
1307+ const fqdn = document.getElementById("fqdn")[0].value;
1308+ const repository = document.getElementById("repository")[0].value;
1309+ let queryUrl = fqdn + "/.well-known/webfinger?resource=" + repository;
1310+ document.getElementById("query-url").innerText = queryUrl;
1311+ doQuery(queryUrl, function(data) {
1312+ let parsed = JSON.parse(data);
1313+ let recorder = newRecorder();
1314+ for (let i = 0; i < parsed.links.length; i++) {
1315+ let link = parsed.links[i];
1316+ if (link["rel"] in _repository) {
1317+ let constraints = _repository[link["rel"]];
1318+ validateLink(link, recorder, constraints);
1319+ }
1320+ };
1321+ let innerHTML = makeTable(recorder.state);
1322+ document.getElementById("results").innerHTML = innerHTML;
1323+ });
1324+
1325+ }
1326 diff --git a/public/webfinger-project/index.html b/public/webfinger-project/index.html
1327new file mode 100644
1328index 0000000..4f10db5
1329--- /dev/null
1330+++ b/public/webfinger-project/index.html
1331 @@ -0,0 +1,346 @@
1332+ <!DOCTYPE html>
1333+ <html lang="en-US">
1334+ <head>
1335+ <meta charset="UTF-8" />
1336+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
1337+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1338+ <title></title>
1339+ <meta
1340+ name="description"
1341+ itemprop="about"
1342+ content="ForgeFeed Specifications"
1343+ />
1344+ <link rel="stylesheet" href="https://forge-feed.org/style.css" />
1345+ </head>
1346+
1347+ <body>
1348+ <div class="wrapper">
1349+ <header>
1350+ <a href="https://forge-feed.org">
1351+ <img
1352+ src="https://forge-feed.org/img/logo.svg"
1353+ class="site-logo"
1354+ alt="Logo"
1355+ width="275px"
1356+ height="auto"
1357+ /></a>
1358+
1359+ <p>ForgeFeed Specifications</p>
1360+
1361+
1362+ <a href="https://codeberg.org/ayllu/forge-feed">
1363+ <img
1364+ src="https://forge-feed.org/img/codeberg.svg"
1365+ class="site-logo"
1366+ alt="Codeberg Logo"
1367+ />
1368+ </a>
1369+
1370+ <a href="https://git.sr.ht/~kevinschoon/forge-feed">
1371+ <img
1372+ src="https://forge-feed.org/img/sourcehut.svg"
1373+ class="site-logo sourcehut"
1374+ alt="Sourcehut Logo"
1375+ />
1376+ </a>
1377+
1378+
1379+
1380+ </header>
1381+
1382+ <section>
1383+ <h1>Project Discovery via WebFinger</h1>
1384+ <p>A project refers to a peice of software which may be spread across multiple
1385+ code <a href="/webfinger-repository">repositories</a>. For example a consider a fictious
1386+ programming language which may be composed of several components such as a
1387+ compiler, a standard library, example codebases, a marketing website, etc.
1388+ When the architecture of such a project spans multiple repositories then it
1389+ is likely they should be represented as a project.</p>
1390+ <p>Sometimes however the scope of a project is much smaller such that it is
1391+ entirely contained within one single repository, alternatively a large project
1392+ might choose to version it's code all in one logical repository. If your forge
1393+ supports it you MAY present a single repository as both an identifiable project
1394+ and also a repository.</p>
1395+ <h1 id="project-uri">Project URI</h1>
1396+ <p>A project URI identifies a software project and optionally the host that is
1397+ resides on. The value serves to contain enough information such that a client
1398+ with a simple URL parser may easily parse the identifier. A project URI MUST
1399+ be a valid <a href="https://datatracker.ietf.org/doc/html/rfc7565">RFC7565</a> URI. The
1400+ slug and hostname parts MUST match the URI path specification as defined in
1401+ <a href="https://www.rfc-editor.org/rfc/rfc3986#section-3.3">RFC3986-3.3</a> while the
1402+ hostname, if specified, must match
1403+ <a href="https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2">RFC3986-3.2.2</a>.</p>
1404+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>project-uri = prefix slug hostname
1405+ </span><span>
1406+ </span><span>prefix = &quot;project://&quot;
1407+ </span><span>slug = rfc3986-path
1408+ </span><span>hostname = [@ rfc3986-hostname]
1409+ </span></code></pre>
1410+ <h3 id="slug">Slug</h3>
1411+ <p>The Slug represents a unique string that identifies a project at a particular
1412+ code forge.</p>
1413+ <h3 id="hostname">Hostname</h3>
1414+ <p>If the hostname part is missing then the address of the server receiving the
1415+ query is assumed. For example the following two queries are equivalent:</p>
1416+ <pre data-lang="text" style="background-color:#2b303b;color:#c0c5ce;" class="language-text "><code class="language-text" data-lang="text"><span>https://example.org/.well-known/webfinger?resource=project://spartacus
1417+ </span><span>https://example.org/.well-known/webfinger?resource=project://spartacus@example.org
1418+ </span></code></pre>
1419+ <h2 id="parsing">Parsing</h2>
1420+ <p>An example parser written in the Python programming language. Any programming
1421+ language which implements a valid RFC7565 parser should be sufficent for
1422+ reading a project URI.</p>
1423+ <pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">from </span><span>urllib.parse </span><span style="color:#b48ead;">import </span><span>urlparse
1424+ </span><span>
1425+ </span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">parse_project</span><span>(</span><span style="color:#bf616a;">text</span><span>):
1426+ </span><span> url = </span><span style="color:#bf616a;">urlparse</span><span>(text)
1427+ </span><span> </span><span style="color:#b48ead;">if </span><span>url.scheme != &quot;</span><span style="color:#a3be8c;">project</span><span>&quot;: </span><span style="color:#65737e;"># schema must be repository://
1428+ </span><span> </span><span style="color:#b48ead;">raise </span><span style="color:#bf616a;">Exception</span><span>(&quot;</span><span style="color:#a3be8c;">Wrong scheme, should be project://</span><span>&quot;)
1429+ </span><span> </span><span style="color:#b48ead;">if </span><span>not url.path:
1430+ </span><span> </span><span style="color:#b48ead;">raise </span><span style="color:#bf616a;">Exception</span><span>(&quot;</span><span style="color:#a3be8c;">Missing slug part</span><span>&quot;)
1431+ </span><span> split = url.path.</span><span style="color:#bf616a;">split</span><span>(&quot;</span><span style="color:#a3be8c;">@</span><span>&quot;, </span><span style="color:#d08770;">1</span><span>)
1432+ </span><span> </span><span style="color:#b48ead;">if </span><span style="color:#96b5b4;">len</span><span>(split) == </span><span style="color:#d08770;">2</span><span>:
1433+ </span><span> domain = </span><span style="color:#bf616a;">urlparse</span><span>(</span><span style="color:#b48ead;">f</span><span>&quot;</span><span style="color:#a3be8c;">ignore://</span><span>{split[</span><span style="color:#d08770;">1</span><span>]}&quot;)
1434+ </span><span> </span><span style="color:#b48ead;">return </span><span>(split[</span><span style="color:#d08770;">0</span><span>], domain.netloc)
1435+ </span><span> </span><span style="color:#b48ead;">return </span><span>(split[</span><span style="color:#d08770;">0</span><span>], </span><span style="color:#d08770;">None</span><span>)
1436+ </span></code></pre>
1437+ <h3 id="relation-types">Relation Types</h3>
1438+ <p>The following <a href="https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.1">Relation Types</a> are
1439+ available for use within a project resource.</p>
1440+ <pre data-lang="text" style="background-color:#2b303b;color:#c0c5ce;" class="language-text "><code class="language-text" data-lang="text"><span>http://forge-feed.org/rel/avatar
1441+ </span><span>http://forge-feed.org/rel/chatroom
1442+ </span><span>http://forge-feed.org/rel/description
1443+ </span><span>http://forge-feed.org/rel/label
1444+ </span><span>http://forge-feed.org/rel/homepage
1445+ </span><span>http://forge-feed.org/rel/repository
1446+ </span><span>http://forge-feed.org/rel/mailing-list
1447+ </span><span>http://forge-feed.org/rel/ticketing-system
1448+ </span></code></pre>
1449+ <h4 id="http-forge-feed-org-rel-avatar"><code>http://forge-feed.org/rel/avatar</code></h4>
1450+ <p>Forges that allow users to configure a logo can expose this information as
1451+ an avatar for use in other applications.</p>
1452+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1453+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://webfinger.net/rel/avatar</span><span>&quot;,
1454+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/stylized-logo.png</span><span>&quot;
1455+ </span><span>}
1456+ </span></code></pre>
1457+ <h4 id="http-feed-forge-org-rel-chatroom"><code>http://feed-forge.org/rel/chatroom</code></h4>
1458+ <p>A chatroom refers to an interactive chat environment for real time
1459+ collaboration among project contributors.</p>
1460+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1461+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://webfinger.net/rel/chatroom</span><span>&quot;,
1462+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">xmpp://spartacus@chat.example.org</span><span>&quot;
1463+ </span><span>}
1464+ </span></code></pre>
1465+ <h4 id="http-forge-feed-org-rel-description"><code>http://forge-feed.org/rel/description</code></h4>
1466+ <p>A brief textual description which tells you something about the project.</p>
1467+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1468+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://example.org/rel/description</span><span>&quot;,
1469+ </span><span> &quot;</span><span style="color:#a3be8c;">titles</span><span>&quot;: {
1470+ </span><span> &quot;</span><span style="color:#a3be8c;">en-us</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">A Text Adventure Written in FORTRAN 77</span><span>&quot;,
1471+ </span><span> &quot;</span><span style="color:#a3be8c;">es</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Una Aventura de Texto Escrita en FORTRAN 77</span><span>&quot;
1472+ </span><span> }
1473+ </span><span>}
1474+ </span></code></pre>
1475+ <h4 id="http-forge-feed-org-rel-label"><code>http://forge-feed.org/rel/label</code></h4>
1476+ <p>A short text based category which can be used for searching.</p>
1477+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1478+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/label</span><span>&quot;,
1479+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1480+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/label</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">text-adventure</span><span>&quot;
1481+ </span><span> }
1482+ </span><span>}
1483+ </span></code></pre>
1484+ <h4 id="http-forge-feed-org-rel-homepage"><code>http://forge-feed.org/rel/homepage</code></h4>
1485+ <p>Link to an HTTP representation of the project homepage.</p>
1486+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1487+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/rel/homepage</span><span>&quot;,
1488+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/example/spartacus</span><span>&quot;
1489+ </span><span>}
1490+ </span></code></pre>
1491+ <h4 id="http-forge-feed-org-rel-mailing-list"><code>http://forge-feed.org/rel/mailing-list</code></h4>
1492+ <p>Links to associated mailing lists, forms, etc.</p>
1493+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1494+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/rel/mailing-list</span><span>&quot;,
1495+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">mailto://list-name@mail.example.org</span><span>&quot;,
1496+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1497+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/mailing-list-subscribe</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">mailto://subscribe+list-name@mail.example.org</span><span>&quot;,
1498+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/mailing-list-unsubscribe</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">mailto://unsubscribe+list-name@mail.example.org</span><span>&quot;
1499+ </span><span> }
1500+ </span><span>}
1501+ </span></code></pre>
1502+ <h4 id="http-forge-feed-org-rel-ticketing-system"><code>http://forge-feed.org/rel/ticketing-system</code></h4>
1503+ <p>Links to issue tracking systems.</p>
1504+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1505+ </span><span> &quot;</span><span style="color:#a3be8c;">ref</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/ticketing-system</span><span>&quot;,
1506+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/bugs</span><span>&quot;,
1507+ </span><span>}
1508+ </span></code></pre>
1509+ <h4 id="http-forge-feed-org-rel-repository"><code>http://forge-feed.org/rel/repository</code></h4>
1510+ <p>Reference to a VCS managed code repository.</p>
1511+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1512+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://webfinger.net/rel/repository</span><span>&quot;,
1513+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://code.example.org/spartacus/spartan-engine</span><span>&quot;,
1514+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1515+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/ns/repository-uri</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">repository:spartacus/spartan-engine</span><span>&quot;,
1516+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/ns/vcs-type</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">git</span><span>&quot;
1517+ </span><span> }
1518+ </span><span>}
1519+ </span></code></pre>
1520+ <h3 id="property-identifiers">Property Identifiers</h3>
1521+ <p>The following <a href="https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.3">property identifiers</a> are available
1522+ for use within a project resource.</p>
1523+ <pre data-lang="text" style="background-color:#2b303b;color:#c0c5ce;" class="language-text "><code class="language-text" data-lang="text"><span>http://forge-feed.org/ns/label
1524+ </span><span>http://forge-feed.org/ns/repository-uri
1525+ </span><span>http://forge-feed.org/ns/vcs-type
1526+ </span><span>http://forge-feed.org/ns/mailing-list-subscribe
1527+ </span><span>http://forge-feed.org/ns/mailing-list-unsubscribe
1528+ </span></code></pre>
1529+ <h4 id="http-forge-feed-org-ns-label"><code>http://forge-feed.org/ns/label</code></h4>
1530+ <p>A short text based category which can be used for searching.</p>
1531+ <h4 id="http-forge-feed-org-ns-repository-uri"><code>http://forge-feed.org/ns/repository-uri</code></h4>
1532+ <p>A Repository URI as described by the <a href="/webfinger-repository">Repository URI</a> specification.</p>
1533+ <h4 id="http-forge-feed-org-ns-vcs-type"><code>http://forge-feed.org/ns/vcs-type</code></h4>
1534+ <p>Identifies VCS types, valid strings are:</p>
1535+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>bzr (GNU Bazaar) bazaar.canonical.com
1536+ </span><span>darcs (Darcs) darcs.net
1537+ </span><span>fossil (Fossil) fossil-scm.org
1538+ </span><span>git (Git) git-scm.com
1539+ </span><span>hg (Mercurial) mercurial-scm.org
1540+ </span><span>pijul (Pijul) pijul.org
1541+ </span><span>svn (Apache Subversion) subversion.apache.org
1542+ </span></code></pre>
1543+ <h4 id="http-forge-feed-org-ns-mailing-list-subscribe"><code>http://forge-feed.org/ns/mailing-list-subscribe</code></h4>
1544+ <p><a href="https://www.rfc-editor.org/rfc/rfc6068">Mailto</a> link for subscribing from a mailing list.</p>
1545+ <h4 id="http-forge-feed-org-ns-mailing-list-unsubscribe"><code>http://forge-feed.org/ns/mailing-list-unsubscribe</code></h4>
1546+ <p><a href="https://www.rfc-editor.org/rfc/rfc6068">Mailto</a> link for unsubscribing from a mailing list.</p>
1547+ <h2 id="example-multi-repository-query">Example Multi-Repository Query</h2>
1548+ <p>A <a href="https://webfinger.net/spec/">WebFinger</a> query may be used to identify
1549+ detailed information about a public project at a particular forge. Here is
1550+ an example response about a fictitious project which has two code repositories
1551+ associated with it as well as chat links, and a bug tracking system.</p>
1552+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>GET https://example.org/.well-known/webfinger?resource=project:spartacus
1553+ </span></code></pre>
1554+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1555+ </span><span> &quot;</span><span style="color:#a3be8c;">subject</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">project:spartacus</span><span>&quot;,
1556+ </span><span> &quot;</span><span style="color:#a3be8c;">aliases</span><span>&quot;: [
1557+ </span><span> &quot;</span><span style="color:#a3be8c;">https://example.org</span><span>&quot;
1558+ </span><span> ],
1559+ </span><span> &quot;</span><span style="color:#a3be8c;">links</span><span>&quot;: [
1560+ </span><span> {
1561+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://webfinger.net/rel/avatar</span><span>&quot;,
1562+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/stylized-logo.png</span><span>&quot;
1563+ </span><span> },
1564+ </span><span> {
1565+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/rel/homepage</span><span>&quot;,
1566+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://code.example.org/spartacus</span><span>&quot;
1567+ </span><span> },
1568+ </span><span> {
1569+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/description</span><span>&quot;,
1570+ </span><span> &quot;</span><span style="color:#a3be8c;">titles</span><span>&quot;: {
1571+ </span><span> &quot;</span><span style="color:#a3be8c;">en-us</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">A Text Adventure Written in FORTRAN 77</span><span>&quot;,
1572+ </span><span> &quot;</span><span style="color:#a3be8c;">es</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Una Aventura de Texto Escrita en FORTRAN 77</span><span>&quot;
1573+ </span><span> }
1574+ </span><span> },
1575+ </span><span> {
1576+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/chatroom</span><span>&quot;,
1577+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">ircs://irc.libera.chat/#spartacus-game</span><span>&quot;,
1578+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1579+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/chatroom</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">irc</span><span>&quot;
1580+ </span><span> }
1581+ </span><span> },
1582+ </span><span> {
1583+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/ticketing-system</span><span>&quot;,
1584+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/bugs</span><span>&quot;
1585+ </span><span> },
1586+ </span><span> {
1587+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/label</span><span>&quot;,
1588+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1589+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/label</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">fortran</span><span>&quot;
1590+ </span><span> }
1591+ </span><span> },
1592+ </span><span> {
1593+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/label</span><span>&quot;,
1594+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1595+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/label</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">text-adventure</span><span>&quot;
1596+ </span><span> }
1597+ </span><span> },
1598+ </span><span> {
1599+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/repository</span><span>&quot;,
1600+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://code.example.org/spartacus/spartan-engine</span><span>&quot;,
1601+ </span><span> &quot;</span><span style="color:#a3be8c;">titles</span><span>&quot;: {
1602+ </span><span> &quot;</span><span style="color:#a3be8c;">en-us</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">The Spartan Game Engine</span><span>&quot;
1603+ </span><span> },
1604+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1605+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/repository-uri</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">repository:spartacus/game-engine</span><span>&quot;
1606+ </span><span> }
1607+ </span><span> },
1608+ </span><span> {
1609+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/repository</span><span>&quot;,
1610+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://code.example.org/spartacus/game</span><span>&quot;,
1611+ </span><span> &quot;</span><span style="color:#a3be8c;">titles</span><span>&quot;: {
1612+ </span><span> &quot;</span><span style="color:#a3be8c;">en-us</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Implementation of the Spartacus Text Adventure game</span><span>&quot;
1613+ </span><span> },
1614+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1615+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/ns/repository-uri</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">repository:spartacus/game</span><span>&quot;,
1616+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/ns/vcs-type</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">git</span><span>&quot;
1617+ </span><span> }
1618+ </span><span> },
1619+ </span><span> {
1620+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/repository</span><span>&quot;,
1621+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://code.example.org/spartacus/game</span><span>&quot;,
1622+ </span><span> &quot;</span><span style="color:#a3be8c;">titles</span><span>&quot;: {
1623+ </span><span> &quot;</span><span style="color:#a3be8c;">en-us</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Promotional Website for the Spartacus Game</span><span>&quot;
1624+ </span><span> },
1625+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1626+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/ns/repository-uri</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">repository:spartacus/www</span><span>&quot;,
1627+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/ns/vcs-type</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">git</span><span>&quot;
1628+ </span><span> }
1629+ </span><span> }
1630+ </span><span> ]
1631+ </span><span>}
1632+ </span></code></pre>
1633+ <h2 id="example-single-repository-query">Example Single Repository Query</h2>
1634+ <p>Here is an example of a project with only a single repository associated with it.</p>
1635+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1636+ </span><span> &quot;</span><span style="color:#a3be8c;">subject</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">project:fuu/bar</span><span>&quot;,
1637+ </span><span> &quot;</span><span style="color:#a3be8c;">links</span><span>&quot;: [
1638+ </span><span> {
1639+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/repository</span><span>&quot;,
1640+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://code.example.org/fuu/bar</span><span>&quot;,
1641+ </span><span> &quot;</span><span style="color:#a3be8c;">titles</span><span>&quot;: {
1642+ </span><span> &quot;</span><span style="color:#a3be8c;">en-us</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Baz Qux</span><span>&quot;
1643+ </span><span> },
1644+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1645+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/repository-uri</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">repository:fuu/bar</span><span>&quot;,
1646+ </span><span> &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/vcs-type</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">git</span><span>&quot;
1647+ </span><span> }
1648+ </span><span> }
1649+ </span><span> ]
1650+ </span><span>}
1651+ </span></code></pre>
1652+ <h3 id="security">Security</h3>
1653+ <p>Projects which are not publicly available should not be identifiable by
1654+ making webfinger queries at all. A project which is private MUST return
1655+ the same response as a repository which does not exist when making a webfinger
1656+ request.</p>
1657+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>GET https://example.org/.well-known/webfinger?resource=project:example/spartacus
1658+ </span><span>200
1659+ </span><span>GET https://example.org/.well-known/webfinger?resource=project:example/private-project
1660+ </span><span>404
1661+ </span><span>GET https://example.org/.well-known/webfinger?resource=project:example/non-existent-project
1662+ </span><span>404
1663+ </span></code></pre>
1664+
1665+ </section>
1666+
1667+ <footer>
1668+ <a href="/LICENSE.txt">
1669+ <img
1670+ src="https://forge-feed.org/img/cc0.png"
1671+ alt="cc0 logo"
1672+ />
1673+ </a>
1674+ </footer>
1675+ </div>
1676+ </body>
1677+ </html>
1678 diff --git a/public/webfinger-repository/index.html b/public/webfinger-repository/index.html
1679new file mode 100644
1680index 0000000..360d93f
1681--- /dev/null
1682+++ b/public/webfinger-repository/index.html
1683 @@ -0,0 +1,262 @@
1684+ <!DOCTYPE html>
1685+ <html lang="en-US">
1686+ <head>
1687+ <meta charset="UTF-8" />
1688+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
1689+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1690+ <title></title>
1691+ <meta
1692+ name="description"
1693+ itemprop="about"
1694+ content="ForgeFeed Specifications"
1695+ />
1696+ <link rel="stylesheet" href="https://forge-feed.org/style.css" />
1697+ </head>
1698+
1699+ <body>
1700+ <div class="wrapper">
1701+ <header>
1702+ <a href="https://forge-feed.org">
1703+ <img
1704+ src="https://forge-feed.org/img/logo.svg"
1705+ class="site-logo"
1706+ alt="Logo"
1707+ width="275px"
1708+ height="auto"
1709+ /></a>
1710+
1711+ <p>ForgeFeed Specifications</p>
1712+
1713+
1714+ <a href="https://codeberg.org/ayllu/forge-feed">
1715+ <img
1716+ src="https://forge-feed.org/img/codeberg.svg"
1717+ class="site-logo"
1718+ alt="Codeberg Logo"
1719+ />
1720+ </a>
1721+
1722+ <a href="https://git.sr.ht/~kevinschoon/forge-feed">
1723+ <img
1724+ src="https://forge-feed.org/img/sourcehut.svg"
1725+ class="site-logo sourcehut"
1726+ alt="Sourcehut Logo"
1727+ />
1728+ </a>
1729+
1730+
1731+
1732+ </header>
1733+
1734+ <section>
1735+ <h1>Repository Discovery via WebFinger</h1>
1736+ <p>A repository refers to a version control managed source code which MAY be
1737+ browsable over HTTP or accessed with specific tooling.</p>
1738+ <h2 id="repository-uri">Repository URI</h2>
1739+ <p>A repository URI identifies a VCS resource and optionally the host that is
1740+ resides on. The value serves to contain enough information such that a client
1741+ with a simple URL parser may easily parse the identifier. A repository URI MUST
1742+ be a valid <a href="https://datatracker.ietf.org/doc/html/rfc7565">RFC7565</a> URI. The
1743+ slug and hostname parts MUST match the URI path specification as defined in
1744+ <a href="https://www.rfc-editor.org/rfc/rfc3986#section-3.3">RFC3986-3.3</a> while the
1745+ hostname, if specified, must match
1746+ <a href="https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2">RFC3986-3.2.2</a>.</p>
1747+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>repository-uri = prefix slug hostname
1748+ </span><span>
1749+ </span><span>prefix = &quot;repository://&quot;
1750+ </span><span>slug = rfc3986-path
1751+ </span><span>hostname = [@ rfc3986-hostname]
1752+ </span></code></pre>
1753+ <h3 id="slug">Slug</h3>
1754+ <p>The Slug represents a unique string that identifies a repository at a
1755+ particular code forge. Repository refers to a VCS managed codebase of some
1756+ kind, e.g. a Git repository.</p>
1757+ <h3 id="hostname">Hostname</h3>
1758+ <p>If the hostname part is missing then the address of the server receiving the
1759+ query is assumed. For example the following two queries are equivalent:</p>
1760+ <pre data-lang="text" style="background-color:#2b303b;color:#c0c5ce;" class="language-text "><code class="language-text" data-lang="text"><span>https://example.org/.well-known/webfinger?resource=repository://spartacus/game
1761+ </span><span>https://example.org/.well-known/webfinger?resource=repository://spartacus/game@example.org
1762+ </span></code></pre>
1763+ <h2 id="parsing">Parsing</h2>
1764+ <p>An example parser written in the Python programming language. Any programming
1765+ language which implements a valid RFC7565 parser should be sufficent for
1766+ reading a project URI.</p>
1767+ <pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">from </span><span>urllib.parse </span><span style="color:#b48ead;">import </span><span>urlparse
1768+ </span><span>
1769+ </span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">from_string</span><span>(</span><span style="color:#bf616a;">text</span><span>):
1770+ </span><span> url = </span><span style="color:#bf616a;">urlparse</span><span>(text)
1771+ </span><span> </span><span style="color:#b48ead;">if </span><span>url.scheme != &quot;</span><span style="color:#a3be8c;">repository</span><span>&quot;: </span><span style="color:#65737e;"># schema must be repository://
1772+ </span><span> </span><span style="color:#b48ead;">raise </span><span style="color:#bf616a;">Exception</span><span>(&quot;</span><span style="color:#a3be8c;">Wrong scheme, should be repository://</span><span>&quot;)
1773+ </span><span> </span><span style="color:#b48ead;">if </span><span>not url.path:
1774+ </span><span> </span><span style="color:#b48ead;">raise </span><span style="color:#bf616a;">Exception</span><span>(&quot;</span><span style="color:#a3be8c;">Missing slug part</span><span>&quot;)
1775+ </span><span> split = url.path.</span><span style="color:#bf616a;">split</span><span>(&quot;</span><span style="color:#a3be8c;">@</span><span>&quot;, </span><span style="color:#d08770;">1</span><span>)
1776+ </span><span> </span><span style="color:#b48ead;">if </span><span style="color:#96b5b4;">len</span><span>(split) == </span><span style="color:#d08770;">2</span><span>:
1777+ </span><span> domain = </span><span style="color:#bf616a;">urlparse</span><span>(</span><span style="color:#b48ead;">f</span><span>&quot;</span><span style="color:#a3be8c;">ignore://</span><span>{split[</span><span style="color:#d08770;">1</span><span>]}&quot;)
1778+ </span><span> </span><span style="color:#b48ead;">return </span><span>(split[</span><span style="color:#d08770;">0</span><span>], domain.netloc)
1779+ </span><span> </span><span style="color:#b48ead;">return </span><span>(split[</span><span style="color:#d08770;">0</span><span>], </span><span style="color:#d08770;">None</span><span>)
1780+ </span></code></pre>
1781+ <h3 id="relation-types">Relation Types</h3>
1782+ <p>The following <a href="https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.1">Relation Types</a> are
1783+ available for use within a repository resource.</p>
1784+ <pre data-lang="text" style="background-color:#2b303b;color:#c0c5ce;" class="language-text "><code class="language-text" data-lang="text"><span>http://forge-feed.org/rel/avatar
1785+ </span><span>http://forge-feed.org/rel/clone
1786+ </span><span>http://forge-feed.org/rel/description
1787+ </span><span>http://forge-feed.org/rel/label
1788+ </span><span>http://forge-feed.org/rel/license
1789+ </span><span>http://forge-feed.org/rel/ticketing-system
1790+ </span></code></pre>
1791+ <h4 id="http-forge-feed-org-rel-avatar"><code>http://forge-feed.org/rel/avatar</code></h4>
1792+ <p>Forges that allow users to configure a logo can expose this information as
1793+ an avatar for use in other applications.</p>
1794+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1795+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://webfinger.net/rel/avatar</span><span>&quot;,
1796+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/stylized-logo.png</span><span>&quot;
1797+ </span><span>}
1798+ </span></code></pre>
1799+ <h4 id="http-forge-feed-org-rel-clone"><code>http://forge-feed.org/rel/clone</code></h4>
1800+ <p>Clone links define how one can download a copy of the remote
1801+ software onto their own server.</p>
1802+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1803+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/rel/clone</span><span>&quot;,
1804+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/example/spartacus</span><span>&quot;,
1805+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1806+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/vcs-type</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">git</span><span>&quot;
1807+ </span><span> }
1808+ </span><span>}
1809+ </span></code></pre>
1810+ <h4 id="http-forge-feed-org-rel-description"><code>http://forge-feed.org/rel/description</code></h4>
1811+ <p>A short text representation of the repository.</p>
1812+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1813+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://example.org/rel/description</span><span>&quot;,
1814+ </span><span> &quot;</span><span style="color:#a3be8c;">titles</span><span>&quot;: {
1815+ </span><span> &quot;</span><span style="color:#a3be8c;">en-us</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">A Text Adventure Written in FORTRAN 77</span><span>&quot;,
1816+ </span><span> &quot;</span><span style="color:#a3be8c;">es</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Una Aventura de Texto Escrita en FORTRAN 77</span><span>&quot;
1817+ </span><span> }
1818+ </span><span>}
1819+ </span></code></pre>
1820+ <h4 id="http-forge-feed-org-rel-label"><code>http://forge-feed.org/rel/label</code></h4>
1821+ <p>A short text based category which can be used for searching.</p>
1822+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1823+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/label</span><span>&quot;,
1824+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1825+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/label</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">text-adventure</span><span>&quot;
1826+ </span><span> }
1827+ </span><span>}
1828+ </span></code></pre>
1829+ <h4 id="http-forge-feed-org-rel-license"><code>http://forge-feed.org/rel/license</code></h4>
1830+ <p>A license <a href="https://spdx.org/licenses/">SPDX identifier</a> and link to the license's full text.</p>
1831+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1832+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/rel/license</span><span>&quot;,
1833+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.com/example/spartacus/tree/LICENSE</span><span>&quot;,
1834+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1835+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/spdx-identifier</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">GPL-2.0-or-later</span><span>&quot;
1836+ </span><span> }
1837+ </span><span>}
1838+ </span></code></pre>
1839+ <h4 id="http-forge-feed-org-rel-ticketing-system"><code>http://forge-feed.org/rel/ticketing-system</code></h4>
1840+ <p>Links to issue tracking systems.</p>
1841+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1842+ </span><span> &quot;</span><span style="color:#a3be8c;">ref</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/ticketing-system</span><span>&quot;,
1843+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/bugs</span><span>&quot;,
1844+ </span><span>}
1845+ </span></code></pre>
1846+ <h3 id="property-identifiers">Property Identifiers</h3>
1847+ <p>The following <a href="https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.3">property identifiers</a> are available
1848+ for use within a repository resource.</p>
1849+ <pre data-lang="text" style="background-color:#2b303b;color:#c0c5ce;" class="language-text "><code class="language-text" data-lang="text"><span>http://forge-feed.org/ns/label
1850+ </span><span>http://forge-feed.org/ns/project-uri
1851+ </span><span>http://forge-feed.org/ns/spdx-identifier
1852+ </span><span>http://forge-feed.org/ns/vcs-type
1853+ </span></code></pre>
1854+ <h4 id="http-forge-feed-org-ns-label"><code>http://forge-feed.org/ns/label</code></h4>
1855+ <p>A short text based category which can be used for searching.</p>
1856+ <h4 id="http-feed-forge-org-ns-spdx-identifier"><code>http://feed-forge.org/ns/spdx-identifier</code></h4>
1857+ <p>Refers to a valid SPDX identifier, see <a href="https://spdx.org/licenses/">license-list</a></p>
1858+ <h4 id="http-feed-forge-org-ns-vcs-type"><code>http://feed-forge.org/ns/vcs-type</code></h4>
1859+ <p>Identifies VCS types, valid strings are:</p>
1860+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>bzr (GNU Bazaar) bazaar.canonical.com
1861+ </span><span>darcs (Darcs) darcs.net
1862+ </span><span>fossil (Fossil) fossil-scm.org
1863+ </span><span>git (Git) git-scm.com
1864+ </span><span>hg (Mercurial) mercurial-scm.org
1865+ </span><span>pijul (Pijul) pijul.org
1866+ </span><span>svn (Apache Subversion) subversion.apache.org
1867+ </span></code></pre>
1868+ <h2 id="example-repository-webfinger-query">Example Repository WebFinger Query</h2>
1869+ <p>A <a href="https://webfinger.net/spec/">WebFinger</a> query may be used to identify
1870+ detailed information about a public repository at a particular forge. Here is
1871+ an example response about a fictitious repository:</p>
1872+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>GET https://example.org/.well-known/webfinger?resource=repository:spartacus/game
1873+ </span></code></pre>
1874+ <pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
1875+ </span><span> &quot;</span><span style="color:#a3be8c;">subject</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">repository:spartacus/game</span><span>&quot;,
1876+ </span><span> &quot;</span><span style="color:#a3be8c;">aliases</span><span>&quot;: [
1877+ </span><span> &quot;</span><span style="color:#a3be8c;">https://example.org/spartacus/game</span><span>&quot;
1878+ </span><span> ],
1879+ </span><span> &quot;</span><span style="color:#a3be8c;">links</span><span>&quot;: [
1880+ </span><span> {
1881+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://webfinger.net/rel/avatar</span><span>&quot;,
1882+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/stylized-logo.png</span><span>&quot;
1883+ </span><span> },
1884+ </span><span> {
1885+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/description</span><span>&quot;,
1886+ </span><span> &quot;</span><span style="color:#a3be8c;">titles</span><span>&quot;: {
1887+ </span><span> &quot;</span><span style="color:#a3be8c;">en-us</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">A Text Adventure Written in FORTRAN 77</span><span>&quot;,
1888+ </span><span> &quot;</span><span style="color:#a3be8c;">es</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Una Aventura de Texto Escrita en FORTRAN 77</span><span>&quot;
1889+ </span><span> }
1890+ </span><span> },
1891+ </span><span> {
1892+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/rel/clone</span><span>&quot;,
1893+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.org/spartacus/game</span><span>&quot;,
1894+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1895+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/vcs-type</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">git</span><span>&quot;
1896+ </span><span> }
1897+ </span><span> },
1898+ </span><span> {
1899+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/license</span><span>&quot;,
1900+ </span><span> &quot;</span><span style="color:#a3be8c;">href</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">https://example.com/spartacus/game/tree/LICENSE</span><span>&quot;,
1901+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1902+ </span><span> &quot;</span><span style="color:#a3be8c;">spdx-identifier</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">GPL-2.0-or-later</span><span>&quot;
1903+ </span><span> }
1904+ </span><span> },
1905+ </span><span> {
1906+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/label</span><span>&quot;,
1907+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1908+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/label</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">fortran</span><span>&quot;
1909+ </span><span> }
1910+ </span><span> },
1911+ </span><span> {
1912+ </span><span> &quot;</span><span style="color:#a3be8c;">rel</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://forge-feed.org/rel/label</span><span>&quot;,
1913+ </span><span> &quot;</span><span style="color:#a3be8c;">properties</span><span>&quot;: {
1914+ </span><span> &quot;</span><span style="color:#a3be8c;">http://feed-forge.org/ns/label</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">text-adventure</span><span>&quot;
1915+ </span><span> }
1916+ </span><span> }
1917+ </span><span> ]
1918+ </span><span>}
1919+ </span></code></pre>
1920+ <h3 id="security">Security</h3>
1921+ <p>Repositories which are not publicly available should not be identifiable by
1922+ making webfinger queries at all. A repository which is private MUST return
1923+ the same response as a repository which does not exist when making a webfinger
1924+ request.</p>
1925+ <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>GET https://example.org/.well-known/webfinger?resource=repository:spartacus/game
1926+ </span><span>200
1927+ </span><span>GET https://example.org/.well-known/webfinger?resource=repository:example/private-repository
1928+ </span><span>404
1929+ </span><span>GET https://example.org/.well-known/webfinger?resource=repository:example/non-existent-repository
1930+ </span><span>404
1931+ </span></code></pre>
1932+
1933+ </section>
1934+
1935+ <footer>
1936+ <a href="/LICENSE.txt">
1937+ <img
1938+ src="https://forge-feed.org/img/cc0.png"
1939+ alt="cc0 logo"
1940+ />
1941+ </a>
1942+ </footer>
1943+ </div>
1944+ </body>
1945+ </html>