unoverse 0.1.196 → 0.1.198

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 +55 -3
  2. package/package.json +2 -1
package/lib/skills.mjs CHANGED
@@ -36,11 +36,63 @@ 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
+
83
+ /**
84
+ * The site sits behind a CDN that serves llms.txt and pages from cache for a while after
85
+ * a release (observed 2026-09-05: ninety minutes, and the new skill pages absent from the
86
+ * listing throughout). An installer that read the cache would install last release's
87
+ * skills right after this one. A fresh query string is a fresh cache key.
88
+ */
89
+ const fresh = (url) => `${url}?t=${Date.now()}`;
90
+
39
91
  export async function installSkills() {
40
92
  const target = join(homedir(), ".claude", "skills");
41
93
  let files;
42
94
  try {
43
- const res = await fetch(`${DOCS}/llms.txt`);
95
+ const res = await fetch(fresh(`${DOCS}/llms.txt`));
44
96
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
45
97
  files = skillFilesFromLlms(await res.text());
46
98
  } catch {
@@ -57,11 +109,11 @@ export async function installSkills() {
57
109
  const tmp = mkdtempSync(join(tmpdir(), "unoverse-skills-"));
58
110
  try {
59
111
  for (const f of files) {
60
- const res = await fetch(f.url);
112
+ const res = await fetch(fresh(f.url));
61
113
  if (!res.ok) throw new Error(`${f.url}: HTTP ${res.status}`);
62
114
  const p = join(tmp, f.skill, f.rel);
63
115
  mkdirSync(dirname(p), { recursive: true });
64
- writeFileSync(p, await res.text());
116
+ writeFileSync(p, skillFileText(f.skill, f.rel, await res.text()));
65
117
  }
66
118
  mkdirSync(target, { recursive: true });
67
119
  // 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.198",
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
  }