llms.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * The handbook as plain Markdown, one page per request.
  5. *
  6. * llms.php index: every page with a summary and its size
  7. * llms.php?doc=KEY one chapter, as written
  8. * llms.php?code=PATH one source file
  9. *
  10. * Apache also answers llms.txt for the index, see .htaccess. That alias is a
  11. * convenience, not the canonical address: it needs mod_rewrite, and this file
  12. * has to work without it.
  13. *
  14. * This is the rendering meant for agents. It exists separately from index.php
  15. * so that a fetch costs only the page that was asked for: the index says what
  16. * each page contains and how large it is, and names the pages a given job
  17. * needs, so nothing has to be pulled in on the chance it might be relevant.
  18. */
  19. require_once __DIR__ . "/inc/handbook.php";
  20. $requestedDoc = isset($_GET["doc"]) ? (string) $_GET["doc"] : "";
  21. $requestedCode = isset($_GET["code"]) ? (string) $_GET["code"] : "";
  22. if ($requestedDoc === "" && $requestedCode === "") {
  23. handbookSendText(handbookRenderIndex());
  24. }
  25. $page = $requestedDoc !== ""
  26. ? handbookPage("doc", $requestedDoc)
  27. : handbookPage("code", $requestedCode);
  28. if ($page === null) {
  29. http_response_code(404);
  30. handbookSendText(
  31. "# Nicht gefunden\n\n"
  32. . "Diese Seite gibt es nicht. Das Verzeichnis aller Seiten steht unter\n"
  33. . handbookSelfUrl() . "/llms.php\n",
  34. );
  35. }
  36. // Chapters link each other by file name; inside this rendering the neighbour
  37. // is another llms.php page, so a following request stays in plain Markdown.
  38. handbookSendText(
  39. rtrim(handbookPageMarkdown($page, "llms.php")) . "\n\n"
  40. . "---\n\n"
  41. . "Verzeichnis aller Seiten: " . handbookSelfUrl() . "/llms.php\n"
  42. . "Diese Seite für Menschen: " . handbookPageUrl($page) . "\n",
  43. );
  44. /** Emits a Markdown document and ends the request. */
  45. function handbookSendText(string $text): void
  46. {
  47. header("Content-Type: text/plain; charset=utf-8");
  48. header("Content-Length: " . (string) strlen($text));
  49. header("X-Content-Type-Options: nosniff");
  50. // Cheap to rebuild, but an agent walking several pages should not pay for
  51. // a revalidation on each one.
  52. header("Cache-Control: public, max-age=300");
  53. echo $text;
  54. exit;
  55. }
  56. /**
  57. * The index: what exists, how big it is, and which pages a given job needs.
  58. */
  59. function handbookRenderIndex(): string
  60. {
  61. $self = handbookSelfUrl();
  62. $docs = handbookPagesOfKind("doc");
  63. $code = handbookPagesOfKind("code");
  64. $total = 0;
  65. foreach (handbookPages() as $page) {
  66. $total += $page["bytes"];
  67. }
  68. $out = [];
  69. $out[] = strtr(handbookRead(HANDBOOK_CONTENT_DIR . "/llms-intro.md"), [
  70. "{{SELF_URL}}" => $self,
  71. "{{BASE_URL}}" => handbookBaseUrl(),
  72. "{{PAGE_COUNT}}" => (string) count(handbookPages()),
  73. "{{TOTAL_SIZE}}" => handbookFormatBytes($total),
  74. ]);
  75. // ---- Recipes: the shortlist per job ------------------------------------
  76. $out[] = "## Wofür welche Seiten\n";
  77. foreach (handbookRecipes() as $recipe) {
  78. $lines = ["### " . $recipe["title"], "", $recipe["note"], ""];
  79. foreach ($recipe["pages"] as [$kind, $key]) {
  80. $page = handbookPage($kind, $key);
  81. if ($page === null) {
  82. continue;
  83. }
  84. $lines[] = "- " . handbookRawUrl($page) . " – " . $page["title"];
  85. }
  86. $out[] = implode("\n", $lines) . "\n";
  87. }
  88. // ---- Every page --------------------------------------------------------
  89. $out[] = handbookRenderList(
  90. "## Dokumentation",
  91. "Kapitel in empfohlener Lesereihenfolge.",
  92. $docs,
  93. );
  94. $out[] = "## Schnittstelle\n\n"
  95. . "- " . $self . "/openapi.php – OpenAPI 3.1 der vier Endpunkte des Manage-Servers, "
  96. . "als JSON. Für einen eigenen Client zusammen mit dem Kapitel *Protokoll v1* lesen.\n"
  97. . "- " . $self . "/api.php – dieselbe Beschreibung in Swagger UI. Eine Seite für "
  98. . "Menschen, für ein Programm ohne Nutzen.\n";
  99. $out[] = handbookRenderList(
  100. "## Quellcode",
  101. "Das vollständige Client-Paket, jede Datei einzeln abrufbar. Für eine Einbindung "
  102. . "wird der Ordner `manage-client/` gebraucht; `examples/`, `docs/` und `scripts/` "
  103. . "gehören nicht dazu.",
  104. $code,
  105. );
  106. $vendored = handbookVendoredFiles();
  107. if ($vendored !== []) {
  108. $lines = ["## Nicht abgedruckt", "", "Fremdbibliotheken, die zum Paket gehören, aber "
  109. . "hier nicht als Seite stehen:", ""];
  110. foreach ($vendored as $relative => $bytes) {
  111. $lines[] = "- `client-package/" . $relative . "` (" . handbookFormatBytes($bytes) . ")";
  112. }
  113. $out[] = implode("\n", $lines) . "\n";
  114. }
  115. return implode("\n", $out);
  116. }
  117. /** @param array<string, array> $pages */
  118. function handbookRenderList(string $heading, string $intro, array $pages): string
  119. {
  120. $lines = [$heading, "", $intro, ""];
  121. foreach ($pages as $page) {
  122. $lines[] = "- " . handbookRawUrl($page)
  123. . " (" . handbookFormatBytes($page["bytes"]) . ")"
  124. . " – **" . $page["title"] . "**"
  125. . ($page["summary"] !== "" ? ". " . $page["summary"] : "");
  126. }
  127. return implode("\n", $lines) . "\n";
  128. }