@vincemakes/kiso-skills-ext 0.44.0 → 0.45.1
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/README.md +11 -0
- package/dist/kiso-skills.mjs +44 -4
- package/index.d.ts +25 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,6 +17,17 @@ Skill directories: `~/.kiso/skills/<name>/SKILL.md` (or the project-level
|
|
|
17
17
|
`.kiso/skills/` after the trust gate). No configuration file — the
|
|
18
18
|
extension scans the skills dir at startup.
|
|
19
19
|
|
|
20
|
+
A name found twice resolves to its first occurrence (root order, then
|
|
21
|
+
directory-name order within each root); the later one is reported with
|
|
22
|
+
the broken skills, never listed.
|
|
23
|
+
|
|
24
|
+
A host passes options instead: `createSkillsExtension({ roots, include })`.
|
|
25
|
+
`roots` replaces the default scan with the host's directories, in order.
|
|
26
|
+
`include(entry)` decides which skills are active, and the model's index,
|
|
27
|
+
`read_skill`, the catalog and the count are all built from that one
|
|
28
|
+
filtered list, so a skill the host turned off cannot be loaded on
|
|
29
|
+
request either.
|
|
30
|
+
|
|
20
31
|
## Invoking a skill yourself
|
|
21
32
|
|
|
22
33
|
`/skill <name> [args]` sends a skill as your turn: its SKILL.md body, then
|
package/dist/kiso-skills.mjs
CHANGED
|
@@ -37,9 +37,19 @@ function kisoHome() {
|
|
|
37
37
|
return process.env.KISO_HOME ?? join(homedir(), ".kiso");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
/**
|
|
41
|
+
* `options` are for a host; the CLI passes none.
|
|
42
|
+
* - `roots`: directories to scan, in order. Given, they replace the
|
|
43
|
+
* default (KISO_SKILLS_DIR, else $KISO_HOME/skills); the env var is not read.
|
|
44
|
+
* - `include(entry)`: false drops the skill from the ONE active index that
|
|
45
|
+
* the prompt, `read_skill`, the catalog and the count are all built
|
|
46
|
+
* from, so no surface can offer a skill another one hides. `broken`
|
|
47
|
+
* (installation diagnostics) is not filtered.
|
|
48
|
+
*/
|
|
49
|
+
export default async function createSkillsExtension(options = {}) {
|
|
50
|
+
const roots = options.roots ?? [process.env.KISO_SKILLS_DIR ?? join(kisoHome(), "skills")];
|
|
51
|
+
const { index: loaded, broken } = firstNameWins(roots.map((root) => loadIndex(root)));
|
|
52
|
+
const index = options.include === undefined ? loaded : loaded.filter((s) => options.include(entryOf(s)));
|
|
43
53
|
// finding #8: no persistent resources — SKILL.md files are read per call;
|
|
44
54
|
// nothing is spawned or connected — no dispose is needed, explicitly.
|
|
45
55
|
const catalog = skillsCatalog(index, broken);
|
|
@@ -137,13 +147,43 @@ function loadIndex(skillsDir) {
|
|
|
137
147
|
return { index, broken };
|
|
138
148
|
}
|
|
139
149
|
|
|
150
|
+
/** A name found twice resolves to its FIRST occurrence — root order, then
|
|
151
|
+
* the directory sort within a root — and every later one is reported in
|
|
152
|
+
* `broken`, never listed. `read_skill` always served the first; the index
|
|
153
|
+
* used to list both, offering a skill nobody could load. Resolved before
|
|
154
|
+
* a host's `include`, so a filter cannot change which one wins. */
|
|
155
|
+
function firstNameWins(scans) {
|
|
156
|
+
const index = [];
|
|
157
|
+
const broken = [];
|
|
158
|
+
const winners = new Map();
|
|
159
|
+
scans.forEach((scan, root) => {
|
|
160
|
+
broken.push(...scan.broken);
|
|
161
|
+
for (const skill of scan.index) {
|
|
162
|
+
const first = winners.get(skill.name);
|
|
163
|
+
if (first === undefined) {
|
|
164
|
+
winners.set(skill.name, { skill, root });
|
|
165
|
+
index.push(skill);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const where = first.root === root ? first.skill.dir : `${first.skill.dir} in an earlier root`;
|
|
169
|
+
broken.push({ dir: skill.dir, reason: `duplicate name "${skill.name}" — already provided by ${where}` });
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return { index, broken };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The published entry shape — the catalog's, and what `include` is shown. */
|
|
176
|
+
function entryOf({ name, description, dir, path, userInvocable }) {
|
|
177
|
+
return { name, description, dir, path, userInvocable };
|
|
178
|
+
}
|
|
179
|
+
|
|
140
180
|
/** 0.40.0 — what the CLI reads to let a PERSON invoke a skill. `body` reads
|
|
141
181
|
* the file at call time (finding #8: nothing is held), strips the
|
|
142
182
|
* frontmatter, and refuses — never truncates — a body over the cap: a
|
|
143
183
|
* skill cut in half is a different instruction than the one written. */
|
|
144
184
|
function skillsCatalog(index, broken) {
|
|
145
185
|
return {
|
|
146
|
-
entries: index.map(
|
|
186
|
+
entries: index.map(entryOf),
|
|
147
187
|
broken: broken.map(({ dir, reason }) => ({ dir, reason })),
|
|
148
188
|
body(name) {
|
|
149
189
|
const skill = index.find((s) => s.name === name);
|
package/index.d.ts
CHANGED
|
@@ -16,20 +16,35 @@ type SkillsExtension = KisoExtension & { readonly skills?: number; readonly cata
|
|
|
16
16
|
/** 0.40.0: the same scan, for the CLI's `/skill` and `/skills`. `body` reads
|
|
17
17
|
* the file at call time and returns the SKILL.md body without its
|
|
18
18
|
* frontmatter, or the reason it cannot (over the cap, unreadable). */
|
|
19
|
+
export interface SkillsCatalogEntry {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly description: string;
|
|
22
|
+
/** the directory under the skills root the skill was found in */
|
|
23
|
+
readonly dir: string;
|
|
24
|
+
readonly path: string;
|
|
25
|
+
/** false only for `user-invocable: false` — the model may still load it */
|
|
26
|
+
readonly userInvocable: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
export interface SkillsCatalog {
|
|
20
|
-
readonly entries: readonly
|
|
21
|
-
readonly name: string;
|
|
22
|
-
readonly description: string;
|
|
23
|
-
/** the directory under the skills root the skill was found in */
|
|
24
|
-
readonly dir: string;
|
|
25
|
-
readonly path: string;
|
|
26
|
-
/** false only for `user-invocable: false` — the model may still load it */
|
|
27
|
-
readonly userInvocable: boolean;
|
|
28
|
-
}[];
|
|
30
|
+
readonly entries: readonly SkillsCatalogEntry[];
|
|
29
31
|
/** the loader's own reason per skipped entry — the words the model's warning line uses */
|
|
30
32
|
readonly broken: readonly { readonly dir: string; readonly reason: string }[];
|
|
31
33
|
body(name: string): { readonly body: string } | { readonly error: string };
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
/** For a host; the CLI passes none, and omitting both keeps the default scan. */
|
|
37
|
+
export interface SkillsExtensionOptions {
|
|
38
|
+
/** Directories to scan, in order. Given → replaces the default
|
|
39
|
+
* (KISO_SKILLS_DIR, else $KISO_HOME/skills); the env var is not read.
|
|
40
|
+
* A name found twice resolves to the first occurrence (root order,
|
|
41
|
+
* then directory order); later ones are reported in `broken`. */
|
|
42
|
+
readonly roots?: readonly string[];
|
|
43
|
+
/** false → the skill is absent from the model's index, from `read_skill`,
|
|
44
|
+
* from the catalog and from the count, which are all built from one
|
|
45
|
+
* filtered list. Runs after duplicates resolve; `broken` is not filtered. */
|
|
46
|
+
readonly include?: (entry: SkillsCatalogEntry) => boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare const createSkillsExtension: (options?: SkillsExtensionOptions) => SkillsExtension | Promise<SkillsExtension>;
|
|
35
50
|
export default createSkillsExtension;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-skills-ext",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.1",
|
|
4
4
|
"description": "kiso official skills extension \u2014 two-tier progressive skills, kernel untouched",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"test": "vitest run"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@vincemakes/kiso-core": "0.
|
|
26
|
+
"@vincemakes/kiso-core": "0.45.1",
|
|
27
27
|
"@types/node": "^26.1.2",
|
|
28
28
|
"typescript": "^5.7.2",
|
|
29
29
|
"vitest": "^3.0.0"
|