Add OpenSkill descriptions for human capabilities and OpenProfile discovery

This commit is contained in:
Anthony Ettinger 2026-09-13 15:47:58 +00:00
parent ba69f58243
commit 25e18f0766
31 changed files with 878 additions and 3 deletions

View file

@ -0,0 +1,59 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { marked } from "marked";
// Public concepts only. Read at build time; request paths never select files.
export const SKILL_SLUGS = [
"branding", "logo-design", "graphic-design", "investing", "investor",
"financial-advisor", "accounting", "accountant", "software-development",
"developer", "software-engineer", "computer-science", "hardware-architectures",
"hardware-architect"
] as const;
const SKILLS_DIR = resolve(process.cwd(), "../../docs/openskill");
export type SkillSummary = {
slug: string;
name: string;
description: string;
kind?: string;
domain?: string;
aliases?: string;
};
export function readSkill(slug: string): string | null {
if (!(SKILL_SLUGS as readonly string[]).includes(slug)) return null;
return readFileSync(resolve(SKILLS_DIR, `${slug}.md`), "utf8");
}
// A catalog view of the introduction, not a lossy replacement for the record.
// Markdown tokenization keeps headings or bullets inside code fences inert.
export function summarizeSkill(markdown: string, slug: string): SkillSummary {
const result: SkillSummary = { slug, name: slug, description: "" };
const tokens = marked.lexer(markdown);
const start = tokens.findIndex((token) => token.type === "heading" && token.depth === 1);
if (start < 0) return result;
const title = tokens[start];
if (title.type === "heading") result.name = title.text;
let identity = true;
for (const token of tokens.slice(start + 1)) {
if (token.type === "heading") break;
if (token.type === "space") continue;
if (token.type === "list" && identity) {
for (const item of token.items) {
const match = /^(?:\*\*)?(Kind|Domain|Aliases)(?:\*\*)?:\s*(.+)$/i.exec(item.text);
if (!match) continue;
const key = match[1].toLowerCase() as "kind" | "domain" | "aliases";
result[key] ??= match[2].trim();
}
} else {
identity = false;
if (token.type === "paragraph" && !result.description) result.description = token.text;
}
}
return result;
}
export function listSkills(): SkillSummary[] {
return SKILL_SLUGS.map((slug) => summarizeSkill(readSkill(slug)!, slug));
}

View file

@ -51,6 +51,7 @@ export const FAMILIES: Family[] = [
"One Markdown file for a person or an agent, served from their own domain and linked from every platform that has a page for them. The profile carries the identity, the accounts and the topics; the sections carry what a platform needs to match on, so a job board, a booking site or a dating app reads the file instead of asking forty questions again.",
specs: [
s("openprofile", "OpenProfile.md", "One Markdown file for who you are and where you are, people and agents alike", { status: "0.2" }),
s("openskill", "OpenSkill", "Portable descriptions of human skills, knowledge and occupations, linked from OpenProfile", { status: "draft" }),
s("openagent", "OpenAgent", "A portable agent profile: identity, owner, skills and requested permissions", { status: "0.1" }),
s("openwall", "OpenWall", "Consent-based broadcasts and direct messages across contact networks, with an AT Protocol mapping", { landing: undefined, status: "draft" }),
s("openresume", "OpenResume.md", "What you have done, in the same spirit, linked from the profile", { landing: undefined }),