@batonfx/skills 0.7.1 → 0.8.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.
@@ -3,18 +3,9 @@ import { HttpClient, HttpClientRequest, HttpClientResponse, Url } from "effect/u
3
3
  import { SkillSource } from "@batonfx/core";
4
4
  import { parseDocument, validateName } from "./skill-document.js";
5
5
  const ManifestSkill = Schema.Struct({
6
- name: Schema.String,
7
- description: Schema.String,
6
+ ...SkillSource.Frontmatter.fields,
8
7
  skillPath: Schema.String,
9
8
  sha256: Schema.String,
10
- whenToUse: Schema.optionalKey(Schema.String),
11
- allowedTools: Schema.optionalKey(Schema.Array(Schema.String)),
12
- disableModelInvocation: Schema.optionalKey(Schema.Boolean),
13
- userInvocable: Schema.optionalKey(Schema.Boolean),
14
- contextFork: Schema.optionalKey(Schema.Boolean),
15
- agent: Schema.optionalKey(Schema.String),
16
- model: Schema.optionalKey(Schema.String),
17
- paths: Schema.optionalKey(Schema.Array(Schema.String)),
18
9
  });
19
10
  const Manifest = Schema.Struct({
20
11
  version: Schema.Literal(1),
@@ -51,18 +42,6 @@ const decodeText = (source, bytes) => Effect.try({
51
42
  try: () => decoder.decode(bytes),
52
43
  catch: (cause) => sourceError(source, "Hosted skill response is not valid UTF-8", cause),
53
44
  });
54
- const frontmatter = (entry) => ({
55
- name: entry.name,
56
- description: entry.description,
57
- ...(entry.whenToUse === undefined ? {} : { whenToUse: entry.whenToUse }),
58
- ...(entry.allowedTools === undefined ? {} : { allowedTools: entry.allowedTools }),
59
- ...(entry.disableModelInvocation === undefined ? {} : { disableModelInvocation: entry.disableModelInvocation }),
60
- ...(entry.userInvocable === undefined ? {} : { userInvocable: entry.userInvocable }),
61
- ...(entry.contextFork === undefined ? {} : { contextFork: entry.contextFork }),
62
- ...(entry.agent === undefined ? {} : { agent: entry.agent }),
63
- ...(entry.model === undefined ? {} : { model: entry.model }),
64
- ...(entry.paths === undefined ? {} : { paths: entry.paths }),
65
- });
66
45
  const sameFrontmatter = (left, right) => JSON.stringify(left) === JSON.stringify(right);
67
46
  /** @experimental Validate one safe relative SKILL.md path. */
68
47
  export const validateSkillPath = Function.dual(2, (source, skillPath) => {
@@ -105,9 +84,6 @@ export const make = (options) => Effect.gen(function* () {
105
84
  const byName = new Map();
106
85
  for (const entry of manifest.skills) {
107
86
  yield* validateName(options.source, entry.name);
108
- if (entry.description.length === 0 || entry.description.length > 1_024) {
109
- return yield* sourceError(options.source, "Hosted skill description must contain 1-1024 characters");
110
- }
111
87
  if (!/^[0-9a-f]{64}$/.test(entry.sha256)) {
112
88
  return yield* sourceError(options.source, `Invalid SHA-256 for hosted skill ${entry.name}`);
113
89
  }
@@ -119,7 +95,7 @@ export const make = (options) => Effect.gen(function* () {
119
95
  return yield* sourceError(options.source, `Hosted skill path directory must match skill name: ${entry.name}`);
120
96
  }
121
97
  const skillUrl = yield* options.resolveSkillUrl(entry.skillPath);
122
- const metadata = frontmatter(entry);
98
+ const { sha256: _, skillPath: __, ...metadata } = entry;
123
99
  const body = fetchBytes(client, options.source, skillUrl, options.bodyHeaders, bodyMaxBytes).pipe(Effect.flatMap((bytes) => crypto.digest("SHA-256", bytes).pipe(Effect.mapError((error) => sourceError(options.source, `Unable to hash hosted skill ${entry.name}`, error)), Effect.flatMap((actual) => Encoding.encodeHex(actual) === entry.sha256
124
100
  ? decodeText(options.source, bytes)
125
101
  : Effect.fail(sourceError(options.source, `SHA-256 mismatch for hosted skill ${entry.name}`))))), Effect.flatMap((content) => parseDocument(options.source, content, entry.name).pipe(Effect.flatMap((document) => sameFrontmatter(document.frontmatter, metadata)
@@ -1,4 +1,4 @@
1
- import { Effect, Function } from "effect";
1
+ import { Effect, Function, Schema } from "effect";
2
2
  import { SkillSource } from "@batonfx/core";
3
3
  const sourceError = (source, message, cause) => SkillSource.SkillSourceError.make({ source, message, ...(cause === undefined ? {} : { cause }) });
4
4
  const normalizeKey = (key) => key.replace(/[-_]/g, "").toLowerCase();
@@ -122,21 +122,7 @@ export const parseFrontmatter = Function.dual(3, (source, block, directoryName)
122
122
  if (parsed.name !== directoryName) {
123
123
  return yield* sourceError(source, `SKILL.md name must match directory ${directoryName}`);
124
124
  }
125
- if (parsed.description === undefined || parsed.description.length === 0 || parsed.description.length > 1_024) {
126
- return yield* sourceError(source, "SKILL.md description must contain 1-1024 characters");
127
- }
128
- return {
129
- name: parsed.name,
130
- description: parsed.description,
131
- ...(parsed.whenToUse === undefined ? {} : { whenToUse: parsed.whenToUse }),
132
- ...(parsed.allowedTools === undefined ? {} : { allowedTools: parsed.allowedTools }),
133
- ...(parsed.disableModelInvocation === undefined ? {} : { disableModelInvocation: parsed.disableModelInvocation }),
134
- ...(parsed.userInvocable === undefined ? {} : { userInvocable: parsed.userInvocable }),
135
- ...(parsed.contextFork === undefined ? {} : { contextFork: parsed.contextFork }),
136
- ...(parsed.agent === undefined ? {} : { agent: parsed.agent }),
137
- ...(parsed.model === undefined ? {} : { model: parsed.model }),
138
- ...(parsed.paths === undefined ? {} : { paths: parsed.paths }),
139
- };
125
+ return yield* Schema.decodeUnknownEffect(SkillSource.Frontmatter)(parsed).pipe(Effect.mapError((cause) => sourceError(source, `SKILL.md description must contain 1-${SkillSource.DESCRIPTION_CAP} characters`, cause)));
140
126
  }));
141
127
  export const parseDocument = Function.dual(3, (source, content, directoryName) => Effect.gen(function* () {
142
128
  const [header, body] = yield* splitDocument(source, content);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@batonfx/skills",
4
- "version": "0.7.1",
4
+ "version": "0.8.1",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "exports": {
@@ -17,7 +17,7 @@
17
17
  "typecheck": "bun tsc --noEmit"
18
18
  },
19
19
  "dependencies": {
20
- "@batonfx/core": "0.7.1",
20
+ "@batonfx/core": "0.8.1",
21
21
  "effect": "4.0.0-beta.98"
22
22
  },
23
23
  "devDependencies": {