@vincemakes/kiso-skills-ext 0.1.45
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/LICENSE +21 -0
- package/README.md +24 -0
- package/dist/kiso-skills.mjs +163 -0
- package/index.d.ts +11 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kiso contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# kiso-skills-ext
|
|
2
|
+
|
|
3
|
+
The kiso official skills extension: two-tier progressive skills, the kernel
|
|
4
|
+
untouched.
|
|
5
|
+
|
|
6
|
+
## How it is loaded
|
|
7
|
+
|
|
8
|
+
Since 0.1.45 this extension ships **built-in** with the kiso CLI — a fresh
|
|
9
|
+
install starts with it registered (the startup banner lists it), with zero
|
|
10
|
+
disk setup. The same artifact can also be installed as a user-level
|
|
11
|
+
extension: copy `dist/kiso-skills.mjs` into `~/.kiso/extensions/` — the
|
|
12
|
+
user-layer loader accepts exactly this shape.
|
|
13
|
+
|
|
14
|
+
## Configuration
|
|
15
|
+
|
|
16
|
+
Skill directories: `~/.kiso/skills/<name>/SKILL.md` (or the project-level
|
|
17
|
+
`.kiso/skills/` after the trust gate). No configuration file — the
|
|
18
|
+
extension scans the skills dir at startup.
|
|
19
|
+
|
|
20
|
+
## Versioning
|
|
21
|
+
|
|
22
|
+
The version counter is this package's own. It is pinned exactly by the kiso
|
|
23
|
+
CLI it ships with; an extension release reaches CLI users through the next
|
|
24
|
+
CLI release.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kiso (foundation) official skills extension — ⑤: two-tier progressive skills,
|
|
3
|
+
* kernel untouched.
|
|
4
|
+
*
|
|
5
|
+
* Tier 1 (resident): the skills index — every ${KISO_SKILLS_DIR:-~/.kiso/
|
|
6
|
+
* skills}/<name>/SKILL.md's frontmatter (a --- wrapped YAML SUBSET; only
|
|
7
|
+
* name/description are read, by a hand-written parser — no deps) becomes
|
|
8
|
+
* one line of the system prompt, sorted by directory name:
|
|
9
|
+
* Available skills (load with read_skill):
|
|
10
|
+
* - <name>: <description>
|
|
11
|
+
* A SKILL.md without frontmatter is skipped with a warning line at the
|
|
12
|
+
* tail of that index (soft failure — the mcp philosophy). No/empty skills
|
|
13
|
+
* dir → an empty extension, never an error.
|
|
14
|
+
*
|
|
15
|
+
* Tier 2 (on demand): the read_skill tool returns the FULL SKILL.md (capped
|
|
16
|
+
* at 32KB with a truncation note); an unknown name is an honest,
|
|
17
|
+
* actionable error listing the installed skills. Files other than
|
|
18
|
+
* SKILL.md are NOT auto-loaded — the body tells the model to read them
|
|
19
|
+
* with read_file by relative path (the progressive third tier; zero new
|
|
20
|
+
* mechanisms).
|
|
21
|
+
*
|
|
22
|
+
* Compatible with Claude Code skills: the frontmatter name/description
|
|
23
|
+
* subset parses CC skill files — drop one in and it works.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
27
|
+
import { homedir } from "node:os";
|
|
28
|
+
import { join } from "node:path";
|
|
29
|
+
|
|
30
|
+
const MAX_DESCRIPTION = 200;
|
|
31
|
+
const MAX_BODY = 32 * 1024;
|
|
32
|
+
|
|
33
|
+
/** finding #11: KISO_HOME is the ONE root — the default skills dir derives
|
|
34
|
+
* from it (KISO_SKILLS_DIR still overrides). */
|
|
35
|
+
function kisoHome() {
|
|
36
|
+
return process.env.KISO_HOME ?? join(homedir(), ".kiso");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default async function createSkillsExtension() {
|
|
40
|
+
const skillsDir = process.env.KISO_SKILLS_DIR ?? join(kisoHome(), "skills");
|
|
41
|
+
const { index, broken } = loadIndex(skillsDir);
|
|
42
|
+
// finding #8: no persistent resources — SKILL.md files are read per call;
|
|
43
|
+
// nothing is spawned or connected — no dispose is needed, explicitly.
|
|
44
|
+
if (index.length === 0 && broken.length === 0) return { name: "skills", tools: [] };
|
|
45
|
+
const tools = index.length > 0 ? [readSkillTool(index, broken)] : [];
|
|
46
|
+
return {
|
|
47
|
+
name: "skills",
|
|
48
|
+
tools,
|
|
49
|
+
systemPrompt: { append: skillsPromptAppend(index, broken) },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Scan ${dir}/<name>/SKILL.md, parse the frontmatter subset, sort by
|
|
54
|
+
* directory name. Broken entries are SOFT failures — recorded, skipped.
|
|
55
|
+
* finding #9 (P2): a symlink to a directory IS a skill dir — the
|
|
56
|
+
* CC-compatible migration path (`ln -s ~/.claude/skills/x
|
|
57
|
+
* ~/.kiso/skills/x`) must work; a broken link (target missing or not a
|
|
58
|
+
* directory) is a soft failure like any other broken skill, never an
|
|
59
|
+
* error. */
|
|
60
|
+
function loadIndex(skillsDir) {
|
|
61
|
+
let dirs;
|
|
62
|
+
let brokenLinks = [];
|
|
63
|
+
try {
|
|
64
|
+
const entries = readdirSync(skillsDir, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
65
|
+
dirs = [];
|
|
66
|
+
for (const d of entries) {
|
|
67
|
+
if (d.isDirectory()) {
|
|
68
|
+
dirs.push(d.name);
|
|
69
|
+
} else if (d.isSymbolicLink()) {
|
|
70
|
+
try {
|
|
71
|
+
if (statSync(join(skillsDir, d.name)).isDirectory()) dirs.push(d.name);
|
|
72
|
+
else brokenLinks.push(`${d.name} (symlink target is not a directory)`);
|
|
73
|
+
} catch {
|
|
74
|
+
brokenLinks.push(`${d.name} (broken symlink)`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
return { index: [], broken: [] }; // no skills dir = no skills, never an error
|
|
80
|
+
}
|
|
81
|
+
const index = [];
|
|
82
|
+
const broken = brokenLinks; // finding #9: broken links join the existing soft-failure path
|
|
83
|
+
for (const dir of dirs) {
|
|
84
|
+
const path = join(skillsDir, dir, "SKILL.md");
|
|
85
|
+
let text;
|
|
86
|
+
try {
|
|
87
|
+
text = readFileSync(path, "utf8");
|
|
88
|
+
} catch {
|
|
89
|
+
broken.push(`${dir} (no SKILL.md)`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const meta = parseFrontmatter(text);
|
|
93
|
+
if (meta === null) {
|
|
94
|
+
broken.push(`${dir} (no frontmatter)`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const name = (meta.name ?? dir).trim();
|
|
98
|
+
let description = (meta.description ?? "").trim();
|
|
99
|
+
if (description === "") {
|
|
100
|
+
broken.push(`${dir} (no description)`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (description.length > MAX_DESCRIPTION) description = `${description.slice(0, MAX_DESCRIPTION)}…[truncated]`;
|
|
104
|
+
index.push({ name, description, path });
|
|
105
|
+
}
|
|
106
|
+
return { index, broken };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The --- wrapped YAML subset: only `name:` and `description:` lines are
|
|
110
|
+
* read (everything else is ignored). Null = no valid frontmatter. */
|
|
111
|
+
function parseFrontmatter(text) {
|
|
112
|
+
if (!text.startsWith("---\n")) return null;
|
|
113
|
+
const end = text.indexOf("\n---", 4);
|
|
114
|
+
if (end < 0) return null;
|
|
115
|
+
const meta = {};
|
|
116
|
+
for (const line of text.slice(4, end).split("\n")) {
|
|
117
|
+
const m = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
|
|
118
|
+
if (m !== null) meta[m[1]] = m[2].trim();
|
|
119
|
+
}
|
|
120
|
+
return meta;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Tier 1: the resident index — one line per skill, sorted by directory
|
|
124
|
+
* name, a warning line for every broken entry at the tail. */
|
|
125
|
+
function skillsPromptAppend(index, broken) {
|
|
126
|
+
const lines = index.map((s) => `- ${s.name}: ${s.description}`);
|
|
127
|
+
const warning =
|
|
128
|
+
broken.length > 0 ? `\n[skills] skipped ${broken.length} broken skill(s): ${broken.join(", ")}` : "";
|
|
129
|
+
return `Available skills (load with read_skill):\n${lines.join("\n")}${warning}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Tier 2: read_skill — the full SKILL.md (≤32KB), or an honest,
|
|
133
|
+
* actionable unknown-name error listing the installed skills. */
|
|
134
|
+
function readSkillTool(index, broken) {
|
|
135
|
+
const brokenNote = broken.length > 0 ? ` (${broken.length} broken skill(s) skipped: ${broken.map((b) => b.split(" ")[0]).join(", ")})` : "";
|
|
136
|
+
return {
|
|
137
|
+
name: "read_skill",
|
|
138
|
+
description: "load a skill's SKILL.md (the available-skills list is in the system prompt)",
|
|
139
|
+
parameters: { type: "object", properties: { name: { type: "string", minLength: 1 } }, required: ["name"] },
|
|
140
|
+
execute: async (input) => {
|
|
141
|
+
const name = String((input ?? {}).name ?? "");
|
|
142
|
+
const skill = index.find((s) => s.name === name);
|
|
143
|
+
if (skill === undefined) {
|
|
144
|
+
const names = index.length > 0 ? index.map((s) => s.name).join(", ") : "(none installed)";
|
|
145
|
+
return {
|
|
146
|
+
content: `[skills] unknown skill "${name}" — available: ${names}${brokenNote}`,
|
|
147
|
+
isError: true,
|
|
148
|
+
errorKind: "invalid_input",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
let text;
|
|
152
|
+
try {
|
|
153
|
+
text = readFileSync(skill.path, "utf8");
|
|
154
|
+
} catch (err) {
|
|
155
|
+
return { content: `[skills] cannot read ${skill.path}: ${err instanceof Error ? err.message : String(err)}`, isError: true, errorKind: "fatal" };
|
|
156
|
+
}
|
|
157
|
+
if (text.length > MAX_BODY) {
|
|
158
|
+
text = `${text.slice(0, MAX_BODY)}\n…[truncated at ${MAX_BODY} chars — read the rest with read_file]`;
|
|
159
|
+
}
|
|
160
|
+
return { content: text, isError: false };
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The published type surface of @vincemakes/kiso-skills-ext: the default
|
|
3
|
+
* export is the FACTORY (the same contract the user-layer disk loader
|
|
4
|
+
* accepts — a KisoExtension or a factory returning one). The type import
|
|
5
|
+
* from kiso-core is compile-time only — the shipped bundle is
|
|
6
|
+
* self-contained, zero runtime dependencies.
|
|
7
|
+
*/
|
|
8
|
+
import type { KisoExtension } from "@vincemakes/kiso-core";
|
|
9
|
+
|
|
10
|
+
declare const createSkillsExtension: () => KisoExtension | Promise<KisoExtension>;
|
|
11
|
+
export default createSkillsExtension;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vincemakes/kiso-skills-ext",
|
|
3
|
+
"version": "0.1.45",
|
|
4
|
+
"description": "kiso official skills extension \u2014 two-tier progressive skills, kernel untouched",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/kiso-skills.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"import": "./dist/kiso-skills.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"index.d.ts",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "node build.mjs",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
23
|
+
"test": "vitest run"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@vincemakes/kiso-core": "0.1.35",
|
|
27
|
+
"@types/node": "^26.1.2",
|
|
28
|
+
"typescript": "^5.7.2",
|
|
29
|
+
"vitest": "^3.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|