From 35eb92828aca2a442c93de1bc46eda1a1a5ac0f4 Mon Sep 17 00:00:00 2001 From: Time Date: Sun, 18 Oct 2026 07:07:39 +0000 Subject: [PATCH] Havre: the skill where agents look (.well-known/skills, the alternate link, robots) (#129) Co-authored-by: Time --- ventures/havre/.well-known/skills/index.json | 11 ++++++ ventures/havre/index.html | 1 + ventures/havre/robots.txt | 4 +++ ventures/havre/tests/agents.test.mjs | 37 ++++++++++++++++++++ ventures/havre/tools/build.sh | 6 ++++ 5 files changed, 59 insertions(+) create mode 100644 ventures/havre/.well-known/skills/index.json create mode 100644 ventures/havre/robots.txt create mode 100644 ventures/havre/tests/agents.test.mjs diff --git a/ventures/havre/.well-known/skills/index.json b/ventures/havre/.well-known/skills/index.json new file mode 100644 index 0000000..476b130 --- /dev/null +++ b/ventures/havre/.well-known/skills/index.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json", + "skills": [ + { + "name": "havre", + "type": "skill-md", + "description": "Write AI characters as Havre cards (.havre.json), export them as Character Card V3 for SillyTavern-style apps, talk to them through Venice's API, and remix someone else's character into an original one, with credit.", + "url": "/skill.md" + } + ] +} diff --git a/ventures/havre/index.html b/ventures/havre/index.html index a929226..241dc81 100644 --- a/ventures/havre/index.html +++ b/ventures/havre/index.html @@ -19,6 +19,7 @@ + diff --git a/ventures/havre/robots.txt b/ventures/havre/robots.txt new file mode 100644 index 0000000..b6b03fc --- /dev/null +++ b/ventures/havre/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://havre.yolopreneur.lol/sitemap.txt diff --git a/ventures/havre/tests/agents.test.mjs b/ventures/havre/tests/agents.test.mjs new file mode 100644 index 0000000..8e5b612 --- /dev/null +++ b/ventures/havre/tests/agents.test.mjs @@ -0,0 +1,37 @@ +// node --test tests/*.test.mjs: where agents look: .well-known/skills/index.json, the alternate links, robots.txt +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; + +const root = new URL("../", import.meta.url); +const read = (p) => readFileSync(new URL(p, root), "utf8"); + +test("the skills index parses and points at a skill file that exists, with the name its front matter gives", () => { + const index = JSON.parse(read(".well-known/skills/index.json")); + assert.ok(Array.isArray(index.skills) && index.skills.length >= 1, "at least one skill"); + for (const s of index.skills) { + assert.ok(s.name && s.description, "a name and a description"); + assert.equal(s.type, "skill-md"); + assert.match(s.url, /^\/[^/]/, "a path from the site root"); + assert.ok(existsSync(new URL("." + s.url, root)), `${s.url} is served from the site root`); + } + const havre = index.skills.find((s) => s.name === "havre"); + assert.ok(havre, "Havre's skill is listed"); + assert.equal(havre.url, "/skill.md"); + assert.match(read("skill.md"), /^---\nname: havre\n/, "skill.md's own front matter agrees on the name"); +}); + +test("index.html carries the alternate links to llms.txt and skill.md, in the head", () => { + const head = read("index.html").split("")[0]; + assert.match(head, //); + assert.match(head, //); + for (const href of ["llms.txt", "skill.md"]) assert.ok(existsSync(new URL(href, root)), `${href} exists`); +}); + +test("robots.txt allows everything and names the sitemap", () => { + const robots = read("robots.txt"); + assert.match(robots, /^User-agent: \*$/m); + assert.match(robots, /^Allow: \/$/m); + assert.doesNotMatch(robots, /^Disallow: *\S/m, "nothing is shut out"); + assert.match(robots, /^Sitemap: https:\/\/havre\.yolopreneur\.lol\/sitemap\.txt$/m); +}); diff --git a/ventures/havre/tools/build.sh b/ventures/havre/tools/build.sh index f1e53bc..a55d2d3 100644 --- a/ventures/havre/tools/build.sh +++ b/ventures/havre/tools/build.sh @@ -14,6 +14,12 @@ cd "$(dirname "$0")/.." # this list.) rm -rf art fonts tools/paint.py tools/draw.py tools/vendor tests/smoke.py +# what agents look for stays: .well-known/ (the skills index) and robots.txt are served as they are, so the build +# never clears them, and fails if they're gone +for f in .well-known/skills/index.json robots.txt; do + [ -f "$f" ] || { echo "tools/build.sh: $f is missing: agents look for it" >&2; exit 1; } +done + node tools/cast.mjs # cast/index.js, the harbor's own as the module the page imports, from cast/*.havre.json node tools/pages.mjs # c/ (a page and a V3 card for each of them, and the list) and sitemap.txt, from the same cards node --test tests/*.test.mjs