unoverse 0.1.196 → 0.1.197

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.
Files changed (2) hide show
  1. package/lib/skills.mjs +45 -1
  2. package/package.json +2 -1
package/lib/skills.mjs CHANGED
@@ -36,6 +36,50 @@ export function skillFilesFromLlms(text, origin = DOCS) {
36
36
  return out;
37
37
  }
38
38
 
39
+ /**
40
+ * A served page, back into the file the skill was authored as.
41
+ *
42
+ * The site does not serve a page's markdown verbatim (observed 2026-09-05): it prepends a
43
+ * "Documentation Index" quote block, renders the frontmatter `description` as a lead
44
+ * quote, and injects an H1 from the filename ("# SKILL", "# Node"). Claude Code needs
45
+ * SKILL.md to open with `name:` and `description:` frontmatter, and a reference to be its
46
+ * own body. So: drop the index block, drop the injected title (it equals the filename
47
+ * stem), lift the lead quote, and for SKILL.md write the frontmatter back. Pure.
48
+ */
49
+ export function pageToSkillFile(rel, text) {
50
+ const lines = text.split("\n");
51
+ let i = 0;
52
+ // 1. the site's index block: leading quoted lines, then a blank
53
+ if (/^> ## Documentation Index/.test(lines[0] ?? "")) {
54
+ while (i < lines.length && lines[i].startsWith(">")) i++;
55
+ while (i < lines.length && lines[i].trim() === "") i++;
56
+ }
57
+ // 2. the frontmatter description, rendered as a lead quote
58
+ let description = "";
59
+ if ((lines[i] ?? "").startsWith("> ")) {
60
+ const quote = [];
61
+ while (i < lines.length && lines[i].startsWith("> ")) quote.push(lines[i++].slice(2));
62
+ description = quote.join(" ").trim();
63
+ while (i < lines.length && lines[i].trim() === "") i++;
64
+ }
65
+ // 3. the injected title, which is the filename stem
66
+ const stem = basename(rel).replace(/\.md$/, "");
67
+ if ((lines[i] ?? "").trim().toLowerCase() === `# ${stem}`.toLowerCase()) {
68
+ i++;
69
+ while (i < lines.length && lines[i].trim() === "") i++;
70
+ }
71
+ const body = lines.slice(i).join("\n").replace(/\s+$/, "") + "\n";
72
+ return { description, body };
73
+ }
74
+
75
+ /** The file to write for one skill page: SKILL.md gets its frontmatter back. */
76
+ export function skillFileText(skill, rel, text) {
77
+ const { description, body } = pageToSkillFile(rel, text);
78
+ if (basename(rel) !== "SKILL.md") return body;
79
+ const desc = JSON.stringify(description || `The ${skill} skill.`);
80
+ return `---\nname: ${skill}\ndescription: ${desc}\n---\n\n${body}`;
81
+ }
82
+
39
83
  export async function installSkills() {
40
84
  const target = join(homedir(), ".claude", "skills");
41
85
  let files;
@@ -61,7 +105,7 @@ export async function installSkills() {
61
105
  if (!res.ok) throw new Error(`${f.url}: HTTP ${res.status}`);
62
106
  const p = join(tmp, f.skill, f.rel);
63
107
  mkdirSync(dirname(p), { recursive: true });
64
- writeFileSync(p, await res.text());
108
+ writeFileSync(p, skillFileText(f.skill, f.rel, await res.text()));
65
109
  }
66
110
  mkdirSync(target, { recursive: true });
67
111
  // REPLACED WHOLE, ours only. Each skill folder the site carries is removed and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.196",
3
+ "version": "0.1.197",
4
4
  "description": "The Unoverse front door — create a Studio project, a universe, or a client app, and launch Studio.",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "type": "module",
@@ -27,6 +27,7 @@
27
27
  "url": "git+https://github.com/unoverse-platform/unoverse.git"
28
28
  },
29
29
  "scripts": {
30
+ "test": "node --test tests/",
30
31
  "prepack": "node scripts/vendor-operator.mjs && node scripts/vendor-base.mjs"
31
32
  }
32
33
  }