@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 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
+ }