Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dist/
# build-generated agent Markdown
public/agent-home.en.md
public/agent-home.ja.md
public/.well-known/agent-skills/index.json

# dependencies
node_modules/
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Everything here is maintained by community members; contributions that make the
| `npm run events:pull` | Refreshes the committed Meetup event cache and fails on errors. |
| `npm run events:pull:stale-ok` | Refreshes Meetup events or preserves the last valid cache. |
| `npm run agent:markdown` | Generates the English and Japanese Markdown responses used for agent content negotiation. |
| `npm run agent:skills` | Generates the agent-skills discovery index and its SHA-256 digests. |
| `npm run feeds:notify` | Polls approved member feeds and posts unseen items to configured channels. |
| `npm run feeds:notify:dry-run` | Shows what the notifier would send without posting or updating state. |
| `npm run preview` | Serves the production build locally. |
Expand Down Expand Up @@ -106,6 +107,7 @@ public/ # Files served as-is (favicon, images)
- Production builds run this generator after the stale-safe event/feed refreshes.
- Cloudflare Pages Functions at `/` and `/ja/` return those files only when the request explicitly accepts `text/markdown`; normal browser requests continue to receive the Astro HTML pages.
- Verify locally or against a preview with `curl -H 'Accept: text/markdown' -D - https://preview-url/`.
- The Markdown maintenance skill is published at `/.well-known/agent-skills/markdown-for-agents/SKILL.md`; `npm run agent:skills` regenerates its discovery index digest during builds.

## Community Feed Notifier

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"astro": "astro",
"astrocheck": "astro check",
"agent:markdown": "node scripts/agent-markdown.mjs",
"build": "npm run feeds:pull:stale-ok && npm run events:pull:stale-ok && npm run agent:markdown && astro build",
"agent:skills": "node scripts/generate-agent-skills-index.mjs",
"build": "npm run feeds:pull:stale-ok && npm run events:pull:stale-ok && npm run agent:markdown && npm run agent:skills && astro build",
"check": "npm run lint; npm run tsc; npm run knip; npm run test; npm run astrocheck; npm run images:check",
"dev": "astro dev",
"feeds:notify": "node scripts/community-feed-notifier.mjs",
Expand Down
31 changes: 31 additions & 0 deletions public/.well-known/agent-skills/markdown-for-agents/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: markdown-for-agents
description: Maintain Kyoto Tech Meetup's localized Markdown responses for AI agents.
---

# Maintain Markdown for Agents

Keep the agent-facing Markdown contract aligned with the normal Astro homepage.

## Source of truth

- `scripts/agent-markdown.mjs` generates the English and Japanese Markdown documents.
- `src/data/meetup-events.json` supplies the event snapshot.
- `src/data/composite-feed.json` supplies the “What members are publishing” items.
- `functions/index.js` and `functions/ja/index.js` negotiate `Accept: text/markdown`.
- `public/_routes.json` keeps the Pages Functions scope limited to `/` and `/ja/`.

## When changing the contract

1. Update both English and Japanese output in `scripts/agent-markdown.mjs`.
2. Keep event, feed, RSVP, map, and community links validated as HTTP(S) URLs.
3. Keep the Markdown concise and factual; do not add API, OAuth, MCP, publishing, or account capabilities that the site does not provide.
4. Update `test/agent-markdown.test.mjs` for content, locale, or negotiation changes.
5. Run `npm run check` and `npm run build`.
6. Verify `/` and `/ja/` with `Accept: text/markdown`, and verify a request without that header still returns HTML.

## Response contract

Markdown responses must return HTTP 200 with `Content-Type: text/markdown; charset=utf-8` and `Vary: Accept`. Normal browser requests must continue to receive the Astro HTML page and the existing security headers.

The generated discovery index contains a digest for this file. Run the build after editing it so the index is regenerated.
32 changes: 32 additions & 0 deletions scripts/generate-agent-skills-index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const SKILL_PATH = path.join(ROOT, "public/.well-known/agent-skills/markdown-for-agents/SKILL.md");
const DEFAULT_OUTPUT_PATH = path.join(ROOT, "public/.well-known/agent-skills/index.json");
const DEFAULT_SKILL_URL = "https://kyototechmeetup.com/.well-known/agent-skills/markdown-for-agents/SKILL.md";

export function buildAgentSkillsIndex(skill, skillUrl = DEFAULT_SKILL_URL) {
const digest = crypto.createHash("sha256").update(skill).digest("hex");
return {
$schema: "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
skills: [{
name: "markdown-for-agents",
type: "skill-md",
description: "Maintain Kyoto Tech Meetup's localized Markdown responses for AI agents.",
url: skillUrl,
digest: `sha256:${digest}`,
}],
};
}

export async function writeAgentSkillsIndex({ skillPath = SKILL_PATH, outputPath = DEFAULT_OUTPUT_PATH, skillUrl = DEFAULT_SKILL_URL } = {}) {
const skill = await fs.readFile(skillPath, "utf8");
const index = buildAgentSkillsIndex(skill, skillUrl);
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, `${JSON.stringify(index, null, 2)}\n`);
}

if (import.meta.url === `file://${process.argv[1]}`) await writeAgentSkillsIndex();
20 changes: 20 additions & 0 deletions test/agent-skills-index.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import crypto from "node:crypto";
import { describe, expect, test } from "vitest";
import { buildAgentSkillsIndex } from "../scripts/generate-agent-skills-index.mjs";

describe("agent skills discovery index", () => {
test("publishes a digest for the Markdown maintenance skill", () => {
const skill = "# Markdown skill\n";
const index = buildAgentSkillsIndex(skill, "https://example.com/SKILL.md");
const expectedDigest = crypto.createHash("sha256").update(skill).digest("hex");

expect(index.$schema).toBe("https://schemas.agentskills.io/discovery/0.2.0/schema.json");
expect(index.skills).toEqual([{
name: "markdown-for-agents",
type: "skill-md",
description: "Maintain Kyoto Tech Meetup's localized Markdown responses for AI agents.",
url: "https://example.com/SKILL.md",
digest: `sha256:${expectedDigest}`,
}]);
});
});
Loading