@yolk-sdk/skillset 0.0.1-canary.0
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 +81 -0
- package/dist/command.d.mts +56 -0
- package/dist/command.d.mts.map +1 -0
- package/dist/command.mjs +135 -0
- package/dist/command.mjs.map +1 -0
- package/dist/errors.d.mts +13 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +18 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +8 -0
- package/dist/manifest.d.mts +33 -0
- package/dist/manifest.d.mts.map +1 -0
- package/dist/manifest.mjs +18 -0
- package/dist/manifest.mjs.map +1 -0
- package/dist/markdown.d.mts +17 -0
- package/dist/markdown.d.mts.map +1 -0
- package/dist/markdown.mjs +41 -0
- package/dist/markdown.mjs.map +1 -0
- package/dist/merge.d.mts +42 -0
- package/dist/merge.d.mts.map +1 -0
- package/dist/merge.mjs +32 -0
- package/dist/merge.mjs.map +1 -0
- package/dist/name.d.mts +10 -0
- package/dist/name.d.mts.map +1 -0
- package/dist/name.mjs +18 -0
- package/dist/name.mjs.map +1 -0
- package/dist/skill.d.mts +30 -0
- package/dist/skill.d.mts.map +1 -0
- package/dist/skill.mjs +47 -0
- package/dist/skill.mjs.map +1 -0
- package/package.json +54 -0
- package/src/command.ts +196 -0
- package/src/errors.ts +16 -0
- package/src/index.ts +19 -0
- package/src/manifest.ts +16 -0
- package/src/markdown.ts +74 -0
- package/src/merge.ts +61 -0
- package/src/name.ts +28 -0
- package/src/skill.ts +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yolk SDK 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,81 @@
|
|
|
1
|
+
# @yolk-sdk/skillset
|
|
2
|
+
|
|
3
|
+
Domain-free parsing and catalog primitives for portable skills and slash commands.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @yolk-sdk/skillset@canary effect
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
|
|
12
|
+
|
|
13
|
+
## Subpaths
|
|
14
|
+
|
|
15
|
+
| Subpath | Purpose |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `@yolk-sdk/skillset` | Skill markdown, command markdown, manifest, and merge APIs |
|
|
18
|
+
|
|
19
|
+
## Imports
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import {
|
|
23
|
+
formatAvailableSkills,
|
|
24
|
+
mergeSkillsets,
|
|
25
|
+
parseCommandMarkdown,
|
|
26
|
+
parseSkillMarkdown,
|
|
27
|
+
renderCommand,
|
|
28
|
+
validateSkillsetName
|
|
29
|
+
} from '@yolk-sdk/skillset'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Skill markdown
|
|
33
|
+
|
|
34
|
+
```md
|
|
35
|
+
---
|
|
36
|
+
name: web-search
|
|
37
|
+
description: Search the web. Use when current public web context is needed.
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
# Web Search
|
|
41
|
+
|
|
42
|
+
Instructions...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const skill = parseSkillMarkdown({ path: 'web-search/SKILL.md', content })
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Command markdown
|
|
50
|
+
|
|
51
|
+
```md
|
|
52
|
+
---
|
|
53
|
+
description: Run a guided package release
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
First load the package-release skill.
|
|
57
|
+
|
|
58
|
+
<user-request>
|
|
59
|
+
$ARGUMENTS
|
|
60
|
+
</user-request>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const command = parseCommandMarkdown({ path: 'package-release.md', content })
|
|
65
|
+
const prompt = renderCommand(command, 'publish first canary')
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Merge model
|
|
69
|
+
|
|
70
|
+
`mergeSkillsets` combines multiple host-provided sources deterministically. Hosts decide source priority and policy.
|
|
71
|
+
|
|
72
|
+
## Host responsibilities
|
|
73
|
+
|
|
74
|
+
- Load skills/commands from filesystem, DB, KV, bundles, or remote sources.
|
|
75
|
+
- Enforce policy, permissions, runtime tool wiring, and UI.
|
|
76
|
+
- Decide when to inject `formatAvailableSkills` into model context.
|
|
77
|
+
|
|
78
|
+
## Boundaries
|
|
79
|
+
|
|
80
|
+
- No filesystem, Next.js, Cloudflare, DB, auth, provider SDKs, or tool execution.
|
|
81
|
+
- Package owns schemas, markdown parsing, command rendering, manifests, and merge helpers only.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SkillsetError } from "./errors.mjs";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
|
|
5
|
+
//#region src/command.d.ts
|
|
6
|
+
declare const CommandArgument: Schema.Struct<{
|
|
7
|
+
readonly name: Schema.String;
|
|
8
|
+
readonly required: Schema.Boolean;
|
|
9
|
+
readonly description: Schema.optional<Schema.String>;
|
|
10
|
+
}>;
|
|
11
|
+
type CommandArgument = typeof CommandArgument.Type;
|
|
12
|
+
declare const CommandAccess: Schema.Union<readonly [Schema.Literal<"read">, Schema.Literal<"write">, Schema.Literal<"destructive">]>;
|
|
13
|
+
type CommandAccess = typeof CommandAccess.Type;
|
|
14
|
+
declare const CommandInfo: Schema.Struct<{
|
|
15
|
+
readonly name: Schema.String;
|
|
16
|
+
readonly description: Schema.optional<Schema.String>;
|
|
17
|
+
readonly template: Schema.String;
|
|
18
|
+
readonly hints: Schema.$Array<Schema.String>;
|
|
19
|
+
readonly arguments: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
20
|
+
readonly name: Schema.String;
|
|
21
|
+
readonly required: Schema.Boolean;
|
|
22
|
+
readonly description: Schema.optional<Schema.String>;
|
|
23
|
+
}>>>;
|
|
24
|
+
readonly access: Schema.optional<Schema.Union<readonly [Schema.Literal<"read">, Schema.Literal<"write">, Schema.Literal<"destructive">]>>;
|
|
25
|
+
readonly fileRefs: Schema.optional<Schema.Boolean>;
|
|
26
|
+
readonly location: Schema.optional<Schema.String>;
|
|
27
|
+
readonly source: Schema.optional<Schema.String>;
|
|
28
|
+
}>;
|
|
29
|
+
type CommandInfo = typeof CommandInfo.Type;
|
|
30
|
+
type ParseCommandInput = {
|
|
31
|
+
readonly markdown: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly location?: string;
|
|
34
|
+
readonly source?: string;
|
|
35
|
+
};
|
|
36
|
+
declare const commandHints: (template: string) => string[];
|
|
37
|
+
declare const parseCommandMarkdown: (input: ParseCommandInput) => Effect.Effect<{
|
|
38
|
+
location: string | undefined;
|
|
39
|
+
source: string | undefined;
|
|
40
|
+
fileRefs?: boolean | undefined;
|
|
41
|
+
access?: "read" | "write" | "destructive" | undefined;
|
|
42
|
+
arguments?: readonly {
|
|
43
|
+
readonly required: boolean;
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly description?: string | undefined;
|
|
46
|
+
}[] | undefined;
|
|
47
|
+
name: string;
|
|
48
|
+
description: string | undefined;
|
|
49
|
+
template: string;
|
|
50
|
+
hints: string[];
|
|
51
|
+
}, SkillsetError, never>;
|
|
52
|
+
declare const parseCommandArguments: (input: string) => string[];
|
|
53
|
+
declare const renderCommand: (command: CommandInfo, argumentsText: string) => string;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { CommandAccess, CommandArgument, CommandInfo, ParseCommandInput, commandHints, parseCommandArguments, parseCommandMarkdown, renderCommand };
|
|
56
|
+
//# sourceMappingURL=command.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.mts","names":[],"sources":["../src/command.ts"],"mappings":";;;;;cAMa,eAAA,EAAe,MAAA,CAAA,MAAA;EAAA;;;;KAKhB,eAAA,UAAyB,eAAA,CAAgB,IAAI;AAAA,cAE5C,aAAA,EAAa,MAAA,CAAA,KAAA,WAAA,MAAA,CAAA,OAAA,UAAA,MAAA,CAAA,OAAA,WAAA,MAAA,CAAA,OAAA;AAAA,KAKd,aAAA,UAAuB,aAAA,CAAc,IAAI;AAAA,cAExC,WAAA,EAAW,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;KAWZ,WAAA,UAAqB,WAAA,CAAY,IAAI;AAAA,KAErC,iBAAA;EAAA,SACD,QAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;AAAA,cAsEE,YAAA,GAAgB,QAAgB;AAAA,cAUhC,oBAAA,GAAwB,KAAA,EAAO,iBAAA,KAAiB,MAAA,CAAA,MAAA;;;;;;;;;;;;;;;cAsBhD,qBAAA,GAAyB,KAAa;AAAA,cAkCtC,aAAA,GAAiB,OAAA,EAAS,WAAW,EAAE,aAAA"}
|
package/dist/command.mjs
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { SkillsetError } from "./errors.mjs";
|
|
2
|
+
import { parseMarkdownDocument } from "./markdown.mjs";
|
|
3
|
+
import { validateSkillsetName } from "./name.mjs";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
//#region src/command.ts
|
|
7
|
+
const CommandArgument = Schema.Struct({
|
|
8
|
+
name: Schema.String,
|
|
9
|
+
required: Schema.Boolean,
|
|
10
|
+
description: Schema.optional(Schema.String)
|
|
11
|
+
});
|
|
12
|
+
const CommandAccess = Schema.Union([
|
|
13
|
+
Schema.Literal("read"),
|
|
14
|
+
Schema.Literal("write"),
|
|
15
|
+
Schema.Literal("destructive")
|
|
16
|
+
]);
|
|
17
|
+
const CommandInfo = Schema.Struct({
|
|
18
|
+
name: Schema.String,
|
|
19
|
+
description: Schema.optional(Schema.String),
|
|
20
|
+
template: Schema.String,
|
|
21
|
+
hints: Schema.Array(Schema.String),
|
|
22
|
+
arguments: Schema.optional(Schema.Array(CommandArgument)),
|
|
23
|
+
access: Schema.optional(CommandAccess),
|
|
24
|
+
fileRefs: Schema.optional(Schema.Boolean),
|
|
25
|
+
location: Schema.optional(Schema.String),
|
|
26
|
+
source: Schema.optional(Schema.String)
|
|
27
|
+
});
|
|
28
|
+
const numberedPlaceholderPattern = /\$(\d+)/g;
|
|
29
|
+
const parseCommandAccess = (value) => {
|
|
30
|
+
if (value === void 0 || value.length === 0) return Effect.succeed(void 0);
|
|
31
|
+
switch (value) {
|
|
32
|
+
case "read":
|
|
33
|
+
case "write":
|
|
34
|
+
case "destructive": return Effect.succeed(value);
|
|
35
|
+
default: return Effect.fail(new SkillsetError({
|
|
36
|
+
cause: "frontmatter_invalid",
|
|
37
|
+
message: "Command access must be read, write, or destructive"
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const parseBooleanField = (field, value) => {
|
|
42
|
+
if (value === void 0 || value.length === 0) return Effect.succeed(void 0);
|
|
43
|
+
switch (value) {
|
|
44
|
+
case "true": return Effect.succeed(true);
|
|
45
|
+
case "false": return Effect.succeed(false);
|
|
46
|
+
default: return Effect.fail(new SkillsetError({
|
|
47
|
+
cause: "frontmatter_invalid",
|
|
48
|
+
message: `${field} must be true or false`
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const parseArgumentToken = (token) => {
|
|
53
|
+
const trimmed = token.trim();
|
|
54
|
+
if (trimmed.length === 0) return;
|
|
55
|
+
const required = !trimmed.endsWith("?");
|
|
56
|
+
const name = required ? trimmed : trimmed.slice(0, -1);
|
|
57
|
+
return name.length === 0 ? void 0 : {
|
|
58
|
+
name,
|
|
59
|
+
required
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const parseCommandArgumentsField = (value) => {
|
|
63
|
+
if (value === void 0 || value.length === 0) return [];
|
|
64
|
+
return value.split(",").flatMap((token) => {
|
|
65
|
+
const argument = parseArgumentToken(token);
|
|
66
|
+
return argument === void 0 ? [] : [argument];
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const commandHints = (template) => {
|
|
70
|
+
const numbered = Array.from(template.matchAll(numberedPlaceholderPattern), (match) => `$${match[1]}`);
|
|
71
|
+
const unique = [...new Set(numbered)].sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)));
|
|
72
|
+
return template.includes("$ARGUMENTS") ? [...unique, "$ARGUMENTS"] : unique;
|
|
73
|
+
};
|
|
74
|
+
const parseCommandMarkdown = (input) => Effect.gen(function* () {
|
|
75
|
+
const name = yield* validateSkillsetName(input.name);
|
|
76
|
+
const document = yield* parseMarkdownDocument(input.markdown);
|
|
77
|
+
const description = document.data.description;
|
|
78
|
+
const access = yield* parseCommandAccess(document.data.access);
|
|
79
|
+
const fileRefs = yield* parseBooleanField("fileRefs", document.data.fileRefs);
|
|
80
|
+
const commandArguments = parseCommandArgumentsField(document.data.arguments);
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
description: description === void 0 || description.length === 0 ? void 0 : description,
|
|
84
|
+
template: document.content,
|
|
85
|
+
hints: commandHints(document.content),
|
|
86
|
+
...commandArguments.length === 0 ? {} : { arguments: commandArguments },
|
|
87
|
+
...access === void 0 ? {} : { access },
|
|
88
|
+
...fileRefs === void 0 ? {} : { fileRefs },
|
|
89
|
+
location: input.location,
|
|
90
|
+
source: input.source
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
const parseCommandArguments = (input) => {
|
|
94
|
+
const result = [];
|
|
95
|
+
let current = "";
|
|
96
|
+
let quote;
|
|
97
|
+
for (const char of input.trim()) {
|
|
98
|
+
if (char === "'" && quote !== "double") {
|
|
99
|
+
quote = quote === "single" ? void 0 : "single";
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (char === "\"" && quote !== "single") {
|
|
103
|
+
quote = quote === "double" ? void 0 : "double";
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (/\s/.test(char) && quote === void 0) {
|
|
107
|
+
if (current.length > 0) {
|
|
108
|
+
result.push(current);
|
|
109
|
+
current = "";
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
current = `${current}${char}`;
|
|
114
|
+
}
|
|
115
|
+
if (current.length > 0) result.push(current);
|
|
116
|
+
return result;
|
|
117
|
+
};
|
|
118
|
+
const renderCommand = (command, argumentsText) => {
|
|
119
|
+
const args = parseCommandArguments(argumentsText);
|
|
120
|
+
const placeholders = Array.from(command.template.matchAll(numberedPlaceholderPattern), (match) => Number(match[1]));
|
|
121
|
+
const lastPlaceholder = placeholders.reduce((max, value) => Math.max(max, value), 0);
|
|
122
|
+
const withNumbered = command.template.replace(numberedPlaceholderPattern, (_, index) => {
|
|
123
|
+
const position = Number(index);
|
|
124
|
+
const argIndex = position - 1;
|
|
125
|
+
if (argIndex >= args.length) return "";
|
|
126
|
+
return position === lastPlaceholder ? args.slice(argIndex).join(" ") : args[argIndex] ?? "";
|
|
127
|
+
});
|
|
128
|
+
const usesArguments = command.template.includes("$ARGUMENTS");
|
|
129
|
+
const rendered = withNumbered.replaceAll("$ARGUMENTS", argumentsText);
|
|
130
|
+
return placeholders.length === 0 && !usesArguments && argumentsText.trim().length > 0 ? `${rendered}\n\n${argumentsText}`.trim() : rendered.trim();
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
export { CommandAccess, CommandArgument, CommandInfo, commandHints, parseCommandArguments, parseCommandMarkdown, renderCommand };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=command.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.mjs","names":[],"sources":["../src/command.ts"],"sourcesContent":["import { Effect } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { SkillsetError } from './errors.ts'\nimport { parseMarkdownDocument } from './markdown.ts'\nimport { validateSkillsetName } from './name.ts'\n\nexport const CommandArgument = Schema.Struct({\n name: Schema.String,\n required: Schema.Boolean,\n description: Schema.optional(Schema.String)\n})\nexport type CommandArgument = typeof CommandArgument.Type\n\nexport const CommandAccess = Schema.Union([\n Schema.Literal('read'),\n Schema.Literal('write'),\n Schema.Literal('destructive')\n])\nexport type CommandAccess = typeof CommandAccess.Type\n\nexport const CommandInfo = Schema.Struct({\n name: Schema.String,\n description: Schema.optional(Schema.String),\n template: Schema.String,\n hints: Schema.Array(Schema.String),\n arguments: Schema.optional(Schema.Array(CommandArgument)),\n access: Schema.optional(CommandAccess),\n fileRefs: Schema.optional(Schema.Boolean),\n location: Schema.optional(Schema.String),\n source: Schema.optional(Schema.String)\n})\nexport type CommandInfo = typeof CommandInfo.Type\n\nexport type ParseCommandInput = {\n readonly markdown: string\n readonly name: string\n readonly location?: string\n readonly source?: string\n}\n\nconst numberedPlaceholderPattern = /\\$(\\d+)/g\n\nconst parseCommandAccess = (value: string | undefined) => {\n if (value === undefined || value.length === 0) {\n return Effect.succeed(undefined)\n }\n\n switch (value) {\n case 'read':\n case 'write':\n case 'destructive':\n return Effect.succeed(value)\n default:\n return Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_invalid',\n message: 'Command access must be read, write, or destructive'\n })\n )\n }\n}\n\nconst parseBooleanField = (field: string, value: string | undefined) => {\n if (value === undefined || value.length === 0) {\n return Effect.succeed(undefined)\n }\n\n switch (value) {\n case 'true':\n return Effect.succeed(true)\n case 'false':\n return Effect.succeed(false)\n default:\n return Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_invalid',\n message: `${field} must be true or false`\n })\n )\n }\n}\n\nconst parseArgumentToken = (token: string) => {\n const trimmed = token.trim()\n\n if (trimmed.length === 0) {\n return undefined\n }\n\n const required = !trimmed.endsWith('?')\n const name = required ? trimmed : trimmed.slice(0, -1)\n\n return name.length === 0 ? undefined : { name, required }\n}\n\nconst parseCommandArgumentsField = (value: string | undefined): ReadonlyArray<CommandArgument> => {\n if (value === undefined || value.length === 0) {\n return []\n }\n\n return value.split(',').flatMap(token => {\n const argument = parseArgumentToken(token)\n\n return argument === undefined ? [] : [argument]\n })\n}\n\nexport const commandHints = (template: string) => {\n const numbered = Array.from(\n template.matchAll(numberedPlaceholderPattern),\n match => `$${match[1]}`\n )\n const unique = [...new Set(numbered)].sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)))\n\n return template.includes('$ARGUMENTS') ? [...unique, '$ARGUMENTS'] : unique\n}\n\nexport const parseCommandMarkdown = (input: ParseCommandInput) =>\n Effect.gen(function* () {\n const name = yield* validateSkillsetName(input.name)\n const document = yield* parseMarkdownDocument(input.markdown)\n const description = document.data.description\n const access = yield* parseCommandAccess(document.data.access)\n const fileRefs = yield* parseBooleanField('fileRefs', document.data.fileRefs)\n const commandArguments = parseCommandArgumentsField(document.data.arguments)\n\n return {\n name,\n description: description === undefined || description.length === 0 ? undefined : description,\n template: document.content,\n hints: commandHints(document.content),\n ...(commandArguments.length === 0 ? {} : { arguments: commandArguments }),\n ...(access === undefined ? {} : { access }),\n ...(fileRefs === undefined ? {} : { fileRefs }),\n location: input.location,\n source: input.source\n }\n })\n\nexport const parseCommandArguments = (input: string) => {\n const result: string[] = []\n let current = ''\n let quote: 'single' | 'double' | undefined\n\n for (const char of input.trim()) {\n if (char === \"'\" && quote !== 'double') {\n quote = quote === 'single' ? undefined : 'single'\n continue\n }\n\n if (char === '\"' && quote !== 'single') {\n quote = quote === 'double' ? undefined : 'double'\n continue\n }\n\n if (/\\s/.test(char) && quote === undefined) {\n if (current.length > 0) {\n result.push(current)\n current = ''\n }\n continue\n }\n\n current = `${current}${char}`\n }\n\n if (current.length > 0) {\n result.push(current)\n }\n\n return result\n}\n\nexport const renderCommand = (command: CommandInfo, argumentsText: string) => {\n const args = parseCommandArguments(argumentsText)\n const placeholders = Array.from(command.template.matchAll(numberedPlaceholderPattern), match =>\n Number(match[1])\n )\n const lastPlaceholder = placeholders.reduce((max, value) => Math.max(max, value), 0)\n const withNumbered = command.template.replace(numberedPlaceholderPattern, (_, index: string) => {\n const position = Number(index)\n const argIndex = position - 1\n\n if (argIndex >= args.length) {\n return ''\n }\n\n return position === lastPlaceholder ? args.slice(argIndex).join(' ') : (args[argIndex] ?? '')\n })\n const usesArguments = command.template.includes('$ARGUMENTS')\n const rendered = withNumbered.replaceAll('$ARGUMENTS', argumentsText)\n\n return placeholders.length === 0 && !usesArguments && argumentsText.trim().length > 0\n ? `${rendered}\\n\\n${argumentsText}`.trim()\n : rendered.trim()\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
|
|
3
|
+
//#region src/errors.d.ts
|
|
4
|
+
declare const SkillsetErrorCause: Schema.Literals<readonly ["duplicate_entry", "frontmatter_field_missing", "frontmatter_invalid", "frontmatter_missing", "invalid_name", "name_mismatch"]>;
|
|
5
|
+
type SkillsetErrorCause = typeof SkillsetErrorCause.Type;
|
|
6
|
+
declare const SkillsetError_base: Schema.Class<SkillsetError, Schema.TaggedStruct<"SkillsetError", {
|
|
7
|
+
readonly cause: Schema.Literals<readonly ["duplicate_entry", "frontmatter_field_missing", "frontmatter_invalid", "frontmatter_missing", "invalid_name", "name_mismatch"]>;
|
|
8
|
+
readonly message: Schema.String;
|
|
9
|
+
}>, import("effect/Cause").YieldableError>;
|
|
10
|
+
declare class SkillsetError extends SkillsetError_base {}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { SkillsetError, SkillsetErrorCause };
|
|
13
|
+
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;;cAEa,kBAAA,EAAkB,MAAA,CAAA,QAAA;AAAA,KAQnB,kBAAA,UAA4B,kBAAA,CAAmB,IAAI;AAAA,cAAA,kBAAA;;;;cAElD,aAAA,SAAsB,kBAGjC"}
|
package/dist/errors.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
const SkillsetErrorCause = Schema.Literals([
|
|
4
|
+
"duplicate_entry",
|
|
5
|
+
"frontmatter_field_missing",
|
|
6
|
+
"frontmatter_invalid",
|
|
7
|
+
"frontmatter_missing",
|
|
8
|
+
"invalid_name",
|
|
9
|
+
"name_mismatch"
|
|
10
|
+
]);
|
|
11
|
+
var SkillsetError = class extends Schema.TaggedErrorClass()("SkillsetError", {
|
|
12
|
+
cause: SkillsetErrorCause,
|
|
13
|
+
message: Schema.String
|
|
14
|
+
}) {};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { SkillsetError, SkillsetErrorCause };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["import * as Schema from 'effect/Schema'\n\nexport const SkillsetErrorCause = Schema.Literals([\n 'duplicate_entry',\n 'frontmatter_field_missing',\n 'frontmatter_invalid',\n 'frontmatter_missing',\n 'invalid_name',\n 'name_mismatch'\n])\nexport type SkillsetErrorCause = typeof SkillsetErrorCause.Type\n\nexport class SkillsetError extends Schema.TaggedErrorClass<SkillsetError>()('SkillsetError', {\n cause: SkillsetErrorCause,\n message: Schema.String\n}) {}\n"],"mappings":";;AAEA,MAAa,qBAAqB,OAAO,SAAS;CAChD;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAGD,IAAa,gBAAb,cAAmC,OAAO,iBAAgC,EAAE,iBAAiB;CAC3F,OAAO;CACP,SAAS,OAAO;AAClB,CAAC,EAAE,CAAC"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SkillsetError, SkillsetErrorCause } from "./errors.mjs";
|
|
2
|
+
import { CommandAccess, CommandArgument, CommandInfo, ParseCommandInput, commandHints, parseCommandArguments, parseCommandMarkdown, renderCommand } from "./command.mjs";
|
|
3
|
+
import { isValidSkillsetName, validateDirectoryName, validateSkillsetName } from "./name.mjs";
|
|
4
|
+
import { MarkdownDocument, parseMarkdownDocument } from "./markdown.mjs";
|
|
5
|
+
import { ParseSkillInput, SkillInfo, formatAvailableSkills, parseSkillMarkdown } from "./skill.mjs";
|
|
6
|
+
import { SkillsetManifest, emptySkillsetManifest } from "./manifest.mjs";
|
|
7
|
+
import { MergedSkillset, SkillsetSource, mergeSkillsets } from "./merge.mjs";
|
|
8
|
+
export { CommandAccess, CommandArgument, CommandInfo, type MarkdownDocument, type MergedSkillset, type ParseCommandInput, type ParseSkillInput, SkillInfo, SkillsetError, SkillsetErrorCause, SkillsetManifest, type SkillsetSource, commandHints, emptySkillsetManifest, formatAvailableSkills, isValidSkillsetName, mergeSkillsets, parseCommandArguments, parseCommandMarkdown, parseMarkdownDocument, parseSkillMarkdown, renderCommand, validateDirectoryName, validateSkillsetName };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SkillsetError, SkillsetErrorCause } from "./errors.mjs";
|
|
2
|
+
import { parseMarkdownDocument } from "./markdown.mjs";
|
|
3
|
+
import { isValidSkillsetName, validateDirectoryName, validateSkillsetName } from "./name.mjs";
|
|
4
|
+
import { CommandAccess, CommandArgument, CommandInfo, commandHints, parseCommandArguments, parseCommandMarkdown, renderCommand } from "./command.mjs";
|
|
5
|
+
import { SkillInfo, formatAvailableSkills, parseSkillMarkdown } from "./skill.mjs";
|
|
6
|
+
import { SkillsetManifest, emptySkillsetManifest } from "./manifest.mjs";
|
|
7
|
+
import { mergeSkillsets } from "./merge.mjs";
|
|
8
|
+
export { CommandAccess, CommandArgument, CommandInfo, SkillInfo, SkillsetError, SkillsetErrorCause, SkillsetManifest, commandHints, emptySkillsetManifest, formatAvailableSkills, isValidSkillsetName, mergeSkillsets, parseCommandArguments, parseCommandMarkdown, parseMarkdownDocument, parseSkillMarkdown, renderCommand, validateDirectoryName, validateSkillsetName };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
|
|
3
|
+
//#region src/manifest.d.ts
|
|
4
|
+
declare const SkillsetManifest: Schema.Struct<{
|
|
5
|
+
readonly version: Schema.Literal<1>;
|
|
6
|
+
readonly skills: Schema.$Array<Schema.Struct<{
|
|
7
|
+
readonly name: Schema.String;
|
|
8
|
+
readonly description: Schema.String;
|
|
9
|
+
readonly location: Schema.String;
|
|
10
|
+
readonly content: Schema.String;
|
|
11
|
+
readonly source: Schema.optional<Schema.String>;
|
|
12
|
+
}>>;
|
|
13
|
+
readonly commands: Schema.$Array<Schema.Struct<{
|
|
14
|
+
readonly name: Schema.String;
|
|
15
|
+
readonly description: Schema.optional<Schema.String>;
|
|
16
|
+
readonly template: Schema.String;
|
|
17
|
+
readonly hints: Schema.$Array<Schema.String>;
|
|
18
|
+
readonly arguments: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
19
|
+
readonly name: Schema.String;
|
|
20
|
+
readonly required: Schema.Boolean;
|
|
21
|
+
readonly description: Schema.optional<Schema.String>;
|
|
22
|
+
}>>>;
|
|
23
|
+
readonly access: Schema.optional<Schema.Union<readonly [Schema.Literal<"read">, Schema.Literal<"write">, Schema.Literal<"destructive">]>>;
|
|
24
|
+
readonly fileRefs: Schema.optional<Schema.Boolean>;
|
|
25
|
+
readonly location: Schema.optional<Schema.String>;
|
|
26
|
+
readonly source: Schema.optional<Schema.String>;
|
|
27
|
+
}>>;
|
|
28
|
+
}>;
|
|
29
|
+
type SkillsetManifest = typeof SkillsetManifest.Type;
|
|
30
|
+
declare const emptySkillsetManifest: SkillsetManifest;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { SkillsetManifest, emptySkillsetManifest };
|
|
33
|
+
//# sourceMappingURL=manifest.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.mts","names":[],"sources":["../src/manifest.ts"],"mappings":";;;cAIa,gBAAA,EAAgB,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;KAKjB,gBAAA,UAA0B,gBAAA,CAAiB,IAAI;AAAA,cAE9C,qBAAA,EAAuB,gBAInC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CommandInfo } from "./command.mjs";
|
|
2
|
+
import { SkillInfo } from "./skill.mjs";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
//#region src/manifest.ts
|
|
5
|
+
const SkillsetManifest = Schema.Struct({
|
|
6
|
+
version: Schema.Literal(1),
|
|
7
|
+
skills: Schema.Array(SkillInfo),
|
|
8
|
+
commands: Schema.Array(CommandInfo)
|
|
9
|
+
});
|
|
10
|
+
const emptySkillsetManifest = {
|
|
11
|
+
version: 1,
|
|
12
|
+
skills: [],
|
|
13
|
+
commands: []
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { SkillsetManifest, emptySkillsetManifest };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=manifest.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.mjs","names":[],"sources":["../src/manifest.ts"],"sourcesContent":["import * as Schema from 'effect/Schema'\nimport { CommandInfo } from './command.ts'\nimport { SkillInfo } from './skill.ts'\n\nexport const SkillsetManifest = Schema.Struct({\n version: Schema.Literal(1),\n skills: Schema.Array(SkillInfo),\n commands: Schema.Array(CommandInfo)\n})\nexport type SkillsetManifest = typeof SkillsetManifest.Type\n\nexport const emptySkillsetManifest: SkillsetManifest = {\n version: 1,\n skills: [],\n commands: []\n}\n"],"mappings":";;;;AAIA,MAAa,mBAAmB,OAAO,OAAO;CAC5C,SAAS,OAAO,QAAQ,CAAC;CACzB,QAAQ,OAAO,MAAM,SAAS;CAC9B,UAAU,OAAO,MAAM,WAAW;AACpC,CAAC;AAGD,MAAa,wBAA0C;CACrD,SAAS;CACT,QAAQ,CAAC;CACT,UAAU,CAAC;AACb"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SkillsetError } from "./errors.mjs";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/markdown.d.ts
|
|
5
|
+
type MarkdownDocument = {
|
|
6
|
+
readonly data: Readonly<Record<string, string>>;
|
|
7
|
+
readonly content: string;
|
|
8
|
+
};
|
|
9
|
+
declare const parseMarkdownDocument: (markdown: string) => Effect.Effect<{
|
|
10
|
+
data: {
|
|
11
|
+
[k: string]: string;
|
|
12
|
+
};
|
|
13
|
+
content: string;
|
|
14
|
+
}, SkillsetError, never>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { MarkdownDocument, parseMarkdownDocument };
|
|
17
|
+
//# sourceMappingURL=markdown.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.mts","names":[],"sources":["../src/markdown.ts"],"mappings":";;;;KAGY,gBAAA;EAAA,SACD,IAAA,EAAM,QAAQ,CAAC,MAAA;EAAA,SACf,OAAA;AAAA;AAAA,cAoBE,qBAAA,GAAyB,QAAA,aAAgB,MAAA,CAAA,MAAA;;;;;GAgDlD,aAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SkillsetError } from "./errors.mjs";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
//#region src/markdown.ts
|
|
4
|
+
const frontmatterStart = "---\n";
|
|
5
|
+
const frontmatterEndIndex = (markdown) => markdown.indexOf("\n---", 4);
|
|
6
|
+
const parseFrontmatterLine = (line) => {
|
|
7
|
+
const separatorIndex = line.indexOf(":");
|
|
8
|
+
if (separatorIndex === -1) return;
|
|
9
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
10
|
+
const value = line.slice(separatorIndex + 1).trim();
|
|
11
|
+
return key.length === 0 ? void 0 : {
|
|
12
|
+
key,
|
|
13
|
+
value
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const parseMarkdownDocument = (markdown) => Effect.gen(function* () {
|
|
17
|
+
if (!markdown.startsWith(frontmatterStart)) return yield* Effect.fail(new SkillsetError({
|
|
18
|
+
cause: "frontmatter_missing",
|
|
19
|
+
message: "Markdown must start with YAML frontmatter"
|
|
20
|
+
}));
|
|
21
|
+
const endIndex = frontmatterEndIndex(markdown);
|
|
22
|
+
if (endIndex === -1) return yield* Effect.fail(new SkillsetError({
|
|
23
|
+
cause: "frontmatter_invalid",
|
|
24
|
+
message: "Markdown frontmatter is missing closing delimiter"
|
|
25
|
+
}));
|
|
26
|
+
const entries = markdown.slice(4, endIndex).split("\n").filter((line) => line.trim().length > 0).map(parseFrontmatterLine);
|
|
27
|
+
if (entries.some((entry) => entry === void 0)) return yield* Effect.fail(new SkillsetError({
|
|
28
|
+
cause: "frontmatter_invalid",
|
|
29
|
+
message: "Markdown frontmatter only supports simple key: value fields"
|
|
30
|
+
}));
|
|
31
|
+
const data = Object.fromEntries(entries.flatMap((entry) => entry === void 0 ? [] : [[entry.key, entry.value]]));
|
|
32
|
+
const contentStart = markdown.startsWith("\n", endIndex + 4) ? endIndex + 4 + 1 : endIndex + 4;
|
|
33
|
+
return {
|
|
34
|
+
data,
|
|
35
|
+
content: markdown.slice(contentStart).trim()
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
export { parseMarkdownDocument };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=markdown.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.mjs","names":[],"sources":["../src/markdown.ts"],"sourcesContent":["import { Effect } from 'effect'\nimport { SkillsetError } from './errors.ts'\n\nexport type MarkdownDocument = {\n readonly data: Readonly<Record<string, string>>\n readonly content: string\n}\n\nconst frontmatterStart = '---\\n'\n\nconst frontmatterEndIndex = (markdown: string) => markdown.indexOf('\\n---', frontmatterStart.length)\n\nconst parseFrontmatterLine = (line: string) => {\n const separatorIndex = line.indexOf(':')\n\n if (separatorIndex === -1) {\n return undefined\n }\n\n const key = line.slice(0, separatorIndex).trim()\n const value = line.slice(separatorIndex + 1).trim()\n\n return key.length === 0 ? undefined : { key, value }\n}\n\nexport const parseMarkdownDocument = (markdown: string) =>\n Effect.gen(function* () {\n if (!markdown.startsWith(frontmatterStart)) {\n return yield* Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_missing',\n message: 'Markdown must start with YAML frontmatter'\n })\n )\n }\n\n const endIndex = frontmatterEndIndex(markdown)\n\n if (endIndex === -1) {\n return yield* Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_invalid',\n message: 'Markdown frontmatter is missing closing delimiter'\n })\n )\n }\n\n const entries = markdown\n .slice(frontmatterStart.length, endIndex)\n .split('\\n')\n .filter(line => line.trim().length > 0)\n .map(parseFrontmatterLine)\n\n if (entries.some(entry => entry === undefined)) {\n return yield* Effect.fail(\n new SkillsetError({\n cause: 'frontmatter_invalid',\n message: 'Markdown frontmatter only supports simple key: value fields'\n })\n )\n }\n\n const data = Object.fromEntries(\n entries.flatMap(entry => (entry === undefined ? [] : [[entry.key, entry.value]]))\n )\n const contentStart = markdown.startsWith('\\n', endIndex + frontmatterStart.length)\n ? endIndex + frontmatterStart.length + 1\n : endIndex + frontmatterStart.length\n\n return {\n data,\n content: markdown.slice(contentStart).trim()\n }\n })\n"],"mappings":";;;AAQA,MAAM,mBAAmB;AAEzB,MAAM,uBAAuB,aAAqB,SAAS,QAAQ,SAAS,CAAuB;AAEnG,MAAM,wBAAwB,SAAiB;CAC7C,MAAM,iBAAiB,KAAK,QAAQ,GAAG;CAEvC,IAAI,mBAAmB,IACrB;CAGF,MAAM,MAAM,KAAK,MAAM,GAAG,cAAc,EAAE,KAAK;CAC/C,MAAM,QAAQ,KAAK,MAAM,iBAAiB,CAAC,EAAE,KAAK;CAElD,OAAO,IAAI,WAAW,IAAI,KAAA,IAAY;EAAE;EAAK;CAAM;AACrD;AAEA,MAAa,yBAAyB,aACpC,OAAO,IAAI,aAAa;CACtB,IAAI,CAAC,SAAS,WAAW,gBAAgB,GACvC,OAAO,OAAO,OAAO,KACnB,IAAI,cAAc;EAChB,OAAO;EACP,SAAS;CACX,CAAC,CACH;CAGF,MAAM,WAAW,oBAAoB,QAAQ;CAE7C,IAAI,aAAa,IACf,OAAO,OAAO,OAAO,KACnB,IAAI,cAAc;EAChB,OAAO;EACP,SAAS;CACX,CAAC,CACH;CAGF,MAAM,UAAU,SACb,MAAM,GAAyB,QAAQ,EACvC,MAAM,IAAI,EACV,QAAO,SAAQ,KAAK,KAAK,EAAE,SAAS,CAAC,EACrC,IAAI,oBAAoB;CAE3B,IAAI,QAAQ,MAAK,UAAS,UAAU,KAAA,CAAS,GAC3C,OAAO,OAAO,OAAO,KACnB,IAAI,cAAc;EAChB,OAAO;EACP,SAAS;CACX,CAAC,CACH;CAGF,MAAM,OAAO,OAAO,YAClB,QAAQ,SAAQ,UAAU,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,KAAK,CAAC,CAAE,CAClF;CACA,MAAM,eAAe,SAAS,WAAW,MAAM,WAAW,CAAuB,IAC7E,WAAW,IAA0B,IACrC,WAAW;CAEf,OAAO;EACL;EACA,SAAS,SAAS,MAAM,YAAY,EAAE,KAAK;CAC7C;AACF,CAAC"}
|
package/dist/merge.d.mts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SkillsetError } from "./errors.mjs";
|
|
2
|
+
import { CommandInfo } from "./command.mjs";
|
|
3
|
+
import { SkillInfo } from "./skill.mjs";
|
|
4
|
+
import { SkillsetManifest } from "./manifest.mjs";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/merge.d.ts
|
|
8
|
+
type SkillsetSource = {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly manifest: SkillsetManifest;
|
|
11
|
+
};
|
|
12
|
+
type MergedSkillset = {
|
|
13
|
+
readonly skills: ReadonlyArray<SkillInfo>;
|
|
14
|
+
readonly commands: ReadonlyArray<CommandInfo>;
|
|
15
|
+
};
|
|
16
|
+
declare const mergeSkillsets: (sources: ReadonlyArray<SkillsetSource>) => Effect.Effect<{
|
|
17
|
+
skills: {
|
|
18
|
+
readonly description: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly location: string;
|
|
21
|
+
readonly content: string;
|
|
22
|
+
readonly source?: string | undefined;
|
|
23
|
+
}[];
|
|
24
|
+
commands: {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly hints: readonly string[];
|
|
27
|
+
readonly template: string;
|
|
28
|
+
readonly description?: string | undefined;
|
|
29
|
+
readonly arguments?: readonly {
|
|
30
|
+
readonly required: boolean;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly description?: string | undefined;
|
|
33
|
+
}[] | undefined;
|
|
34
|
+
readonly access?: "read" | "write" | "destructive" | undefined;
|
|
35
|
+
readonly fileRefs?: boolean | undefined;
|
|
36
|
+
readonly location?: string | undefined;
|
|
37
|
+
readonly source?: string | undefined;
|
|
38
|
+
}[];
|
|
39
|
+
}, SkillsetError, never>;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { MergedSkillset, SkillsetSource, mergeSkillsets };
|
|
42
|
+
//# sourceMappingURL=merge.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.d.mts","names":[],"sources":["../src/merge.ts"],"mappings":";;;;;;;KAMY,cAAA;EAAA,SACD,EAAA;EAAA,SACA,QAAA,EAAU,gBAAgB;AAAA;AAAA,KAGzB,cAAA;EAAA,SACD,MAAA,EAAQ,aAAA,CAAc,SAAA;EAAA,SACtB,QAAA,EAAU,aAAA,CAAc,WAAA;AAAA;AAAA,cAuCtB,cAAA,GAAkB,OAAA,EAAS,aAAA,CAAc,cAAA,MAAe,MAAA,CAAA,MAAA"}
|
package/dist/merge.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SkillsetError } from "./errors.mjs";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
//#region src/merge.ts
|
|
4
|
+
const mergeByName = (entries) => {
|
|
5
|
+
const byName = /* @__PURE__ */ new Map();
|
|
6
|
+
[...entries].reverse().forEach((group) => {
|
|
7
|
+
group.forEach((entry) => byName.set(entry.name, entry));
|
|
8
|
+
});
|
|
9
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
10
|
+
};
|
|
11
|
+
const duplicateNames = (entries) => {
|
|
12
|
+
const names = entries.map((entry) => entry.name);
|
|
13
|
+
return [...new Set(names.filter((name, index) => names.indexOf(name) !== index))];
|
|
14
|
+
};
|
|
15
|
+
const rejectInternalDuplicates = (source) => {
|
|
16
|
+
const duplicates = [...duplicateNames(source.manifest.skills).map((name) => `skill:${name}`), ...duplicateNames(source.manifest.commands).map((name) => `command:${name}`)];
|
|
17
|
+
return duplicates.length === 0 ? Effect.void : Effect.fail(new SkillsetError({
|
|
18
|
+
cause: "duplicate_entry",
|
|
19
|
+
message: `Duplicate entries in source ${source.id}: ${duplicates.join(", ")}`
|
|
20
|
+
}));
|
|
21
|
+
};
|
|
22
|
+
const mergeSkillsets = (sources) => Effect.gen(function* () {
|
|
23
|
+
yield* Effect.forEach(sources, rejectInternalDuplicates, { discard: true });
|
|
24
|
+
return {
|
|
25
|
+
skills: mergeByName(sources.map((source) => source.manifest.skills)),
|
|
26
|
+
commands: mergeByName(sources.map((source) => source.manifest.commands))
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { mergeSkillsets };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=merge.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.mjs","names":[],"sources":["../src/merge.ts"],"sourcesContent":["import { Effect } from 'effect'\nimport { SkillsetError } from './errors.ts'\nimport type { CommandInfo } from './command.ts'\nimport type { SkillInfo } from './skill.ts'\nimport type { SkillsetManifest } from './manifest.ts'\n\nexport type SkillsetSource = {\n readonly id: string\n readonly manifest: SkillsetManifest\n}\n\nexport type MergedSkillset = {\n readonly skills: ReadonlyArray<SkillInfo>\n readonly commands: ReadonlyArray<CommandInfo>\n}\n\nconst mergeByName = <Entry extends { readonly name: string }>(\n entries: ReadonlyArray<ReadonlyArray<Entry>>\n) => {\n const byName = new Map<string, Entry>()\n\n const reversedEntries = [...entries].reverse()\n\n reversedEntries.forEach(group => {\n group.forEach(entry => byName.set(entry.name, entry))\n })\n\n return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name))\n}\n\nconst duplicateNames = <Entry extends { readonly name: string }>(entries: ReadonlyArray<Entry>) => {\n const names = entries.map(entry => entry.name)\n\n return [...new Set(names.filter((name, index) => names.indexOf(name) !== index))]\n}\n\nconst rejectInternalDuplicates = (source: SkillsetSource) => {\n const duplicates = [\n ...duplicateNames(source.manifest.skills).map(name => `skill:${name}`),\n ...duplicateNames(source.manifest.commands).map(name => `command:${name}`)\n ]\n\n return duplicates.length === 0\n ? Effect.void\n : Effect.fail(\n new SkillsetError({\n cause: 'duplicate_entry',\n message: `Duplicate entries in source ${source.id}: ${duplicates.join(', ')}`\n })\n )\n}\n\nexport const mergeSkillsets = (sources: ReadonlyArray<SkillsetSource>) =>\n Effect.gen(function* () {\n yield* Effect.forEach(sources, rejectInternalDuplicates, { discard: true })\n\n return {\n skills: mergeByName(sources.map(source => source.manifest.skills)),\n commands: mergeByName(sources.map(source => source.manifest.commands))\n }\n })\n"],"mappings":";;;AAgBA,MAAM,eACJ,YACG;CACH,MAAM,yBAAS,IAAI,IAAmB;CAItC,CAFyB,GAAG,OAAO,EAAE,QAEvB,EAAE,SAAQ,UAAS;EAC/B,MAAM,SAAQ,UAAS,OAAO,IAAI,MAAM,MAAM,KAAK,CAAC;CACtD,CAAC;CAED,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACzE;AAEA,MAAM,kBAA2D,YAAkC;CACjG,MAAM,QAAQ,QAAQ,KAAI,UAAS,MAAM,IAAI;CAE7C,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,QAAQ,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAAK,CAAC,CAAC;AAClF;AAEA,MAAM,4BAA4B,WAA2B;CAC3D,MAAM,aAAa,CACjB,GAAG,eAAe,OAAO,SAAS,MAAM,EAAE,KAAI,SAAQ,SAAS,MAAM,GACrE,GAAG,eAAe,OAAO,SAAS,QAAQ,EAAE,KAAI,SAAQ,WAAW,MAAM,CAC3E;CAEA,OAAO,WAAW,WAAW,IACzB,OAAO,OACP,OAAO,KACL,IAAI,cAAc;EAChB,OAAO;EACP,SAAS,+BAA+B,OAAO,GAAG,IAAI,WAAW,KAAK,IAAI;CAC5E,CAAC,CACH;AACN;AAEA,MAAa,kBAAkB,YAC7B,OAAO,IAAI,aAAa;CACtB,OAAO,OAAO,QAAQ,SAAS,0BAA0B,EAAE,SAAS,KAAK,CAAC;CAE1E,OAAO;EACL,QAAQ,YAAY,QAAQ,KAAI,WAAU,OAAO,SAAS,MAAM,CAAC;EACjE,UAAU,YAAY,QAAQ,KAAI,WAAU,OAAO,SAAS,QAAQ,CAAC;CACvE;AACF,CAAC"}
|
package/dist/name.d.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SkillsetError } from "./errors.mjs";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/name.d.ts
|
|
5
|
+
declare const isValidSkillsetName: (name: string) => boolean;
|
|
6
|
+
declare const validateSkillsetName: (name: string) => Effect.Effect<string, never, never> | Effect.Effect<never, SkillsetError, never>;
|
|
7
|
+
declare const validateDirectoryName: (expected: string, actual: string) => Effect.Effect<string, never, never> | Effect.Effect<never, SkillsetError, never>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { isValidSkillsetName, validateDirectoryName, validateSkillsetName };
|
|
10
|
+
//# sourceMappingURL=name.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name.d.mts","names":[],"sources":["../src/name.ts"],"mappings":";;;;cAMa,mBAAA,GAAuB,IAAY;AAAA,cAGnC,oBAAA,GAAwB,IAAA,aAAY,MAAA,CAAA,MAAA,yBAAA,MAAA,CAAA,MAAA,QAAA,aAAA;AAAA,cAUpC,qBAAA,GAAyB,QAAA,UAAkB,MAAA,aAAc,MAAA,CAAA,MAAA,yBAAA,MAAA,CAAA,MAAA,QAAA,aAAA"}
|
package/dist/name.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SkillsetError } from "./errors.mjs";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
//#region src/name.ts
|
|
4
|
+
const skillsetNamePattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
5
|
+
const maxSkillsetNameLength = 64;
|
|
6
|
+
const isValidSkillsetName = (name) => name.length > 0 && name.length <= maxSkillsetNameLength && skillsetNamePattern.test(name);
|
|
7
|
+
const validateSkillsetName = (name) => isValidSkillsetName(name) ? Effect.succeed(name) : Effect.fail(new SkillsetError({
|
|
8
|
+
cause: "invalid_name",
|
|
9
|
+
message: `Invalid skillset entry name: ${name}`
|
|
10
|
+
}));
|
|
11
|
+
const validateDirectoryName = (expected, actual) => expected === actual ? Effect.succeed(expected) : Effect.fail(new SkillsetError({
|
|
12
|
+
cause: "name_mismatch",
|
|
13
|
+
message: `Skill name must match directory name: expected ${expected}, got ${actual}`
|
|
14
|
+
}));
|
|
15
|
+
//#endregion
|
|
16
|
+
export { isValidSkillsetName, validateDirectoryName, validateSkillsetName };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=name.mjs.map
|