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.
- package/lib/skills.mjs +55 -3
- 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.
|
|
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
|
}
|