@hgdc/agents 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/build.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // version Markdown de chaque page au build et produit les routes qui la servent sur
3
3
  // `Accept: text/markdown`. Rien ne s'exécute à la requête : tout est fichier statique et route.
4
4
  import { readdir, readFile, writeFile } from "node:fs/promises";
5
- import { htmlToMarkdown } from "./markdown.js";
5
+ import { htmlToMarkdown, pageFrontmatter } from "./markdown.js";
6
6
  const NOT_FOUND = "404.html";
7
7
  // Valeur de condition commune à Host (edge/src/router.ts) et Vercel : correspondance sur toute la valeur.
8
8
  const ACCEPTS_MARKDOWN = { type: "header", key: "accept", value: ".*text/markdown.*" };
@@ -42,7 +42,7 @@ export async function writeMarkdownPages(dir, options) {
42
42
  const html = await readFile(new URL(file, dir), "utf-8");
43
43
  const url = `${options.site}${path ?? `${base}/`}`;
44
44
  const target = notFound ? file.replace(/\.html$/, ".md") : markdownFile(path, base);
45
- await writeFile(new URL(target, dir), htmlToMarkdown(html, url) + footer);
45
+ await writeFile(new URL(target, dir), pageFrontmatter(html, url) + htmlToMarkdown(html, url) + footer);
46
46
  if (path !== null)
47
47
  pages.push({ path, file: target });
48
48
  }
package/dist/fetch.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Ce qu'une app rendue côté serveur (TanStack Start, ou tout handler `fetch`) montre aux agents : la page en
2
2
  // Markdown, ses données structurées et son adresse canonique, les pages de confiance, des erreurs d'API en JSON.
3
3
  import { canonicalLink, injectIntoHead, jsonLdScript } from "./head.js";
4
- import { htmlToMarkdown } from "./markdown.js";
4
+ import { htmlToMarkdown, pageFrontmatter } from "./markdown.js";
5
5
  import { prefersMarkdown, varyOnAccept } from "./negotiate.js";
6
6
  /** Pied de page standard : une ligne de liens, `[libellé](adresse)` séparés par « · ». */
7
7
  export function linksFooter(links) {
@@ -40,7 +40,13 @@ export function withMarkdown(handler, options = {}) {
40
40
  const redirected = response.status >= 300 && response.status < 400;
41
41
  if (!markdown || response.status < 200 || redirected)
42
42
  return withHeaders(response, request.method === "HEAD" ? null : enrichHead(response, url, options), varyOnAccept);
43
- const text = htmlToMarkdown(await response.text(), request.url) + (options.footer ? `\n---\n\n${options.footer}\n` : "");
43
+ const html = await response.text();
44
+ // Métadonnées en tête ; l'adresse canonique seulement si l'origine publique est connue (une console
45
+ // relayée voit l'adresse de son relais, pas celle du visiteur).
46
+ const canonical = options.canonicalOrigin ? `${options.canonicalOrigin}${url.pathname}` : undefined;
47
+ const text = pageFrontmatter(html, canonical) +
48
+ htmlToMarkdown(html, request.url) +
49
+ (options.footer ? `\n---\n\n${options.footer}\n` : "");
44
50
  return withHeaders(response, request.method === "HEAD" ? null : text, (out) => {
45
51
  for (const name of BODY_HEADERS)
46
52
  out.delete(name);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { htmlToMarkdown, pageTitle } from "./markdown.js";
1
+ export { htmlToMarkdown, pageFrontmatter, pageTitle } from "./markdown.js";
2
2
  export { prefersMarkdown, varyOnAccept } from "./negotiate.js";
3
3
  export { linksFooter, withMarkdown, type MarkdownOptions } from "./fetch.js";
4
4
  export { canonicalLink, injectIntoHead, jsonLdScript } from "./head.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { htmlToMarkdown, pageTitle } from "./markdown.js";
1
+ export { htmlToMarkdown, pageFrontmatter, pageTitle } from "./markdown.js";
2
2
  export { prefersMarkdown, varyOnAccept } from "./negotiate.js";
3
3
  export { linksFooter, withMarkdown } from "./fetch.js";
4
4
  export { canonicalLink, injectIntoHead, jsonLdScript } from "./head.js";
@@ -1,7 +1,9 @@
1
1
  /** Titre de la page (`<title>`), pour une page dont le contenu n'a pas de titre de niveau 1. */
2
2
  export declare function pageTitle(html: string): string | null;
3
3
  /**
4
- * Markdown du contenu d'une page HTML.
5
- * @param base adresse de la page, pour rendre les liens absolus
4
+ * Bloc de métadonnées YAML qui ouvre le Markdown d'une page : titre, description et adresse canonique, lus
5
+ * dans le `<head>` (`url` à défaut de `<link rel="canonical">`, sinon pas d'adresse). Les valeurs sont des
6
+ * chaînes JSON, du YAML valide quel que soit leur contenu.
6
7
  */
8
+ export declare function pageFrontmatter(html: string, url?: string | URL): string;
7
9
  export declare function htmlToMarkdown(html: string, base: string | URL): string;
package/dist/markdown.js CHANGED
@@ -243,6 +243,35 @@ export function pageTitle(html) {
243
243
  * Markdown du contenu d'une page HTML.
244
244
  * @param base adresse de la page, pour rendre les liens absolus
245
245
  */
246
+ const attribute = (tag, name) => new RegExp(`\\s${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "i")
247
+ .exec(tag)
248
+ ?.slice(1)
249
+ .find((v) => v !== undefined);
250
+ /** Première balise `<tag …>` du document dont un attribut vaut `value`, avec son attribut `read`. */
251
+ function headValue(html, tag, key, value, read) {
252
+ for (const m of html.matchAll(new RegExp(`<${tag}\\b[^>]*>`, "gi"))) {
253
+ if (attribute(m[0], key)?.toLowerCase() === value) {
254
+ const found = attribute(m[0], read);
255
+ if (found)
256
+ return collapse(decode(found)).trim();
257
+ }
258
+ }
259
+ return null;
260
+ }
261
+ /**
262
+ * Bloc de métadonnées YAML qui ouvre le Markdown d'une page : titre, description et adresse canonique, lus
263
+ * dans le `<head>` (`url` à défaut de `<link rel="canonical">`, sinon pas d'adresse). Les valeurs sont des
264
+ * chaînes JSON, du YAML valide quel que soit leur contenu.
265
+ */
266
+ export function pageFrontmatter(html, url) {
267
+ const fields = [
268
+ ["title", pageTitle(html)],
269
+ ["description", headValue(html, "meta", "name", "description", "content")],
270
+ ["canonical", headValue(html, "link", "rel", "canonical", "href") ?? (url ? new URL(url).href : null)],
271
+ ];
272
+ const lines = fields.filter(([, v]) => v).map(([k, v]) => `${k}: ${JSON.stringify(v)}`);
273
+ return lines.length ? `---\n${lines.join("\n")}\n---\n\n` : "";
274
+ }
246
275
  export function htmlToMarkdown(html, base) {
247
276
  const page = new URL(base);
248
277
  const link = (href) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/agents",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Sites lisibles par les agents IA : version Markdown des pages (Accept: text/markdown), 404 en Markdown, routes Build Output API pour les sites prérendus.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/agents#readme",