@batonfx/skills 0.4.0 → 0.4.2
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 +5 -0
- package/dist/github-catalog.d.ts +18 -0
- package/dist/github-catalog.js +53 -0
- package/dist/hosted-catalog.d.ts +64 -0
- package/dist/hosted-catalog.js +147 -0
- package/dist/http-catalog.d.ts +13 -0
- package/dist/http-catalog.js +19 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -37363
- package/dist/instructions-files.d.ts +14 -0
- package/dist/instructions-files.js +42 -0
- package/dist/s3-catalog.d.ts +16 -0
- package/dist/s3-catalog.js +33 -0
- package/dist/skill-document.d.ts +10 -0
- package/dist/skill-document.js +144 -0
- package/dist/skill-loader.d.ts +13 -0
- package/dist/skill-loader.js +56 -0
- package/package.json +29 -4
- package/.turbo/turbo-build.log +0 -5
- package/src/github-catalog.ts +0 -80
- package/src/hosted-catalog.ts +0 -270
- package/src/http-catalog.ts +0 -29
- package/src/index.ts +0 -6
- package/src/instructions-files.ts +0 -63
- package/src/s3-catalog.ts +0 -49
- package/src/skill-document.ts +0 -174
- package/src/skill-loader.ts +0 -112
- package/test/hosted-catalog.test.ts +0 -398
- package/test/instructions-files.test.ts +0 -57
- package/test/skill-loader.test.ts +0 -248
- package/tsconfig.json +0 -7
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Crypto } from "effect";
|
|
2
|
+
import { HttpClient } from "effect/unstable/http";
|
|
3
|
+
import { SkillSource } from "@batonfx/core";
|
|
4
|
+
import { type Limits } from "./hosted-catalog.js";
|
|
5
|
+
/** @experimental Manifest-backed GitHub catalog options. */
|
|
6
|
+
export interface Options extends Limits {
|
|
7
|
+
readonly owner: string;
|
|
8
|
+
readonly repo: string;
|
|
9
|
+
readonly ref: string;
|
|
10
|
+
readonly root?: string;
|
|
11
|
+
readonly manifestName?: string;
|
|
12
|
+
readonly apiBaseUrl?: string;
|
|
13
|
+
readonly source?: string;
|
|
14
|
+
}
|
|
15
|
+
/** @experimental Build a manifest-backed immutable GitHub catalog source. */
|
|
16
|
+
export declare const make: (options: Options) => SkillSource.Source<HttpClient.HttpClient | Crypto.Crypto>;
|
|
17
|
+
/** @experimental Build a manifest-backed immutable GitHub catalog layer. */
|
|
18
|
+
export declare const layer: (options: Options) => import("effect/Layer").Layer<SkillSource.SkillSource, SkillSource.SkillSourceError, HttpClient.HttpClient | Crypto.Crypto>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Crypto, Effect } from "effect";
|
|
2
|
+
import { HttpClient, Url } from "effect/unstable/http";
|
|
3
|
+
import { SkillSource } from "@batonfx/core";
|
|
4
|
+
import { make as makeHostedCatalog, validateSkillPath } from "./hosted-catalog.js";
|
|
5
|
+
const encodedPath = (value) => value
|
|
6
|
+
.split("/")
|
|
7
|
+
.filter((segment) => segment.length > 0)
|
|
8
|
+
.map(encodeURIComponent)
|
|
9
|
+
.join("/");
|
|
10
|
+
/** @experimental Build a manifest-backed immutable GitHub catalog source. */
|
|
11
|
+
export const make = (options) => {
|
|
12
|
+
const source = options.source ?? `github:${options.owner}/${options.repo}@${options.ref}`;
|
|
13
|
+
if (!/^[0-9a-fA-F]{40}$|^[0-9a-fA-F]{64}$/.test(options.ref)) {
|
|
14
|
+
return Effect.fail(new SkillSource.SkillSourceError({ source, message: "GitHub skill catalog ref must be a commit id" }));
|
|
15
|
+
}
|
|
16
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(options.owner) ||
|
|
17
|
+
!/^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/.test(options.repo)) {
|
|
18
|
+
return Effect.fail(new SkillSource.SkillSourceError({ source, message: "Invalid GitHub owner or repository" }));
|
|
19
|
+
}
|
|
20
|
+
return Effect.gen(function* () {
|
|
21
|
+
if ((options.root?.length ?? 0) > 0)
|
|
22
|
+
yield* validateSkillPath(source, options.root ?? "");
|
|
23
|
+
yield* validateSkillPath(source, options.manifestName ?? "skills.json");
|
|
24
|
+
const apiBase = yield* Effect.fromResult(Url.fromString(options.apiBaseUrl ?? "https://api.github.com")).pipe(Effect.mapError((cause) => new SkillSource.SkillSourceError({ source, message: "Invalid GitHub API base URL", cause })));
|
|
25
|
+
if (apiBase.protocol !== "https:" ||
|
|
26
|
+
apiBase.username.length > 0 ||
|
|
27
|
+
apiBase.password.length > 0 ||
|
|
28
|
+
apiBase.search.length > 0 ||
|
|
29
|
+
apiBase.hash.length > 0) {
|
|
30
|
+
return yield* Effect.fail(new SkillSource.SkillSourceError({ source, message: "Invalid GitHub API base URL" }));
|
|
31
|
+
}
|
|
32
|
+
const base = apiBase.toString().replace(/\/$/, "");
|
|
33
|
+
const root = encodedPath(options.root ?? "");
|
|
34
|
+
const manifestName = encodedPath(options.manifestName ?? "skills.json");
|
|
35
|
+
const repository = `${base}/repos/${encodeURIComponent(options.owner)}/${encodeURIComponent(options.repo)}/contents`;
|
|
36
|
+
const rootUrl = `${repository}/${root.length === 0 ? "" : `${root}/`}`;
|
|
37
|
+
const manifestUrl = `${rootUrl}${manifestName}?ref=${encodeURIComponent(options.ref)}`;
|
|
38
|
+
const headers = {
|
|
39
|
+
accept: "application/vnd.github.raw+json",
|
|
40
|
+
"x-github-api-version": "2022-11-28",
|
|
41
|
+
};
|
|
42
|
+
return yield* makeHostedCatalog({
|
|
43
|
+
...options,
|
|
44
|
+
source,
|
|
45
|
+
manifestUrl,
|
|
46
|
+
manifestHeaders: headers,
|
|
47
|
+
bodyHeaders: headers,
|
|
48
|
+
resolveSkillUrl: (skillPath) => validateSkillPath(source, skillPath).pipe(Effect.map((safePath) => `${rootUrl}${encodedPath(safePath)}?ref=${encodeURIComponent(options.ref)}`)),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
/** @experimental Build a manifest-backed immutable GitHub catalog layer. */
|
|
53
|
+
export const layer = (options) => SkillSource.layer([make(options)]);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Crypto, Effect, Layer, Schema } from "effect";
|
|
2
|
+
import { HttpClient } from "effect/unstable/http";
|
|
3
|
+
import { SkillSource } from "@batonfx/core";
|
|
4
|
+
/** @experimental Baton hosted skill manifest entry. */
|
|
5
|
+
export declare const ManifestSkill: Schema.Struct<{
|
|
6
|
+
readonly name: Schema.String;
|
|
7
|
+
readonly description: Schema.String;
|
|
8
|
+
readonly skillPath: Schema.String;
|
|
9
|
+
readonly sha256: Schema.String;
|
|
10
|
+
readonly whenToUse: Schema.optionalKey<Schema.String>;
|
|
11
|
+
readonly allowedTools: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
12
|
+
readonly disableModelInvocation: Schema.optionalKey<Schema.Boolean>;
|
|
13
|
+
readonly userInvocable: Schema.optionalKey<Schema.Boolean>;
|
|
14
|
+
readonly contextFork: Schema.optionalKey<Schema.Boolean>;
|
|
15
|
+
readonly agent: Schema.optionalKey<Schema.String>;
|
|
16
|
+
readonly model: Schema.optionalKey<Schema.String>;
|
|
17
|
+
readonly paths: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
18
|
+
}>;
|
|
19
|
+
/** @experimental */
|
|
20
|
+
export type ManifestSkill = typeof ManifestSkill.Type;
|
|
21
|
+
/** @experimental Baton hosted skill manifest. */
|
|
22
|
+
export declare const Manifest: Schema.Struct<{
|
|
23
|
+
readonly version: Schema.Literal<1>;
|
|
24
|
+
readonly skills: Schema.$Array<Schema.Struct<{
|
|
25
|
+
readonly name: Schema.String;
|
|
26
|
+
readonly description: Schema.String;
|
|
27
|
+
readonly skillPath: Schema.String;
|
|
28
|
+
readonly sha256: Schema.String;
|
|
29
|
+
readonly whenToUse: Schema.optionalKey<Schema.String>;
|
|
30
|
+
readonly allowedTools: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
31
|
+
readonly disableModelInvocation: Schema.optionalKey<Schema.Boolean>;
|
|
32
|
+
readonly userInvocable: Schema.optionalKey<Schema.Boolean>;
|
|
33
|
+
readonly contextFork: Schema.optionalKey<Schema.Boolean>;
|
|
34
|
+
readonly agent: Schema.optionalKey<Schema.String>;
|
|
35
|
+
readonly model: Schema.optionalKey<Schema.String>;
|
|
36
|
+
readonly paths: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
37
|
+
}>>;
|
|
38
|
+
}>;
|
|
39
|
+
/** @experimental */
|
|
40
|
+
export type Manifest = typeof Manifest.Type;
|
|
41
|
+
/** @experimental Shared hosted-catalog limits and trusted tools. */
|
|
42
|
+
export interface Limits {
|
|
43
|
+
readonly manifestMaxBytes?: number;
|
|
44
|
+
readonly bodyMaxBytes?: number;
|
|
45
|
+
readonly maxSkills?: number;
|
|
46
|
+
readonly descriptionCap?: number;
|
|
47
|
+
readonly toolsBySkill?: Readonly<Record<string, ReadonlyArray<SkillSource.Skill["tools"][number]>>>;
|
|
48
|
+
}
|
|
49
|
+
/** @experimental Internal hosted-catalog construction options. */
|
|
50
|
+
export interface MakeOptions extends Limits {
|
|
51
|
+
readonly source: string;
|
|
52
|
+
readonly manifestUrl: string;
|
|
53
|
+
readonly resolveSkillUrl: (skillPath: string) => Effect.Effect<string, SkillSource.SkillSourceError>;
|
|
54
|
+
readonly manifestHeaders?: Readonly<Record<string, string>>;
|
|
55
|
+
readonly bodyHeaders?: Readonly<Record<string, string>>;
|
|
56
|
+
}
|
|
57
|
+
/** @experimental Validate one safe relative SKILL.md path. */
|
|
58
|
+
export declare const validateSkillPath: (source: string, skillPath: string) => Effect.Effect<string, SkillSource.SkillSourceError>;
|
|
59
|
+
/** @experimental Resolve a same-origin path beneath a manifest directory. */
|
|
60
|
+
export declare const resolveRelative: (source: string, manifestUrl: string, skillPath: string) => Effect.Effect<string, SkillSource.SkillSourceError>;
|
|
61
|
+
/** @experimental Build a hosted manifest source over Effect HTTP and Crypto services. */
|
|
62
|
+
export declare const make: (options: MakeOptions) => SkillSource.Source<HttpClient.HttpClient | Crypto.Crypto>;
|
|
63
|
+
/** @experimental One-source hosted catalog layer. */
|
|
64
|
+
export declare const layer: (options: MakeOptions) => Layer.Layer<SkillSource.SkillSource, SkillSource.SkillSourceError, HttpClient.HttpClient | Crypto.Crypto>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Crypto, Effect, Encoding, Layer, Schema, Stream } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientRequest, HttpClientResponse, Url } from "effect/unstable/http";
|
|
3
|
+
import { SkillSource } from "@batonfx/core";
|
|
4
|
+
import { parseDocument, validateName } from "./skill-document.js";
|
|
5
|
+
/** @experimental Baton hosted skill manifest entry. */
|
|
6
|
+
export const ManifestSkill = Schema.Struct({
|
|
7
|
+
name: Schema.String,
|
|
8
|
+
description: Schema.String,
|
|
9
|
+
skillPath: Schema.String,
|
|
10
|
+
sha256: Schema.String,
|
|
11
|
+
whenToUse: Schema.optionalKey(Schema.String),
|
|
12
|
+
allowedTools: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
13
|
+
disableModelInvocation: Schema.optionalKey(Schema.Boolean),
|
|
14
|
+
userInvocable: Schema.optionalKey(Schema.Boolean),
|
|
15
|
+
contextFork: Schema.optionalKey(Schema.Boolean),
|
|
16
|
+
agent: Schema.optionalKey(Schema.String),
|
|
17
|
+
model: Schema.optionalKey(Schema.String),
|
|
18
|
+
paths: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
19
|
+
});
|
|
20
|
+
/** @experimental Baton hosted skill manifest. */
|
|
21
|
+
export const Manifest = Schema.Struct({
|
|
22
|
+
version: Schema.Literal(1),
|
|
23
|
+
skills: Schema.Array(ManifestSkill),
|
|
24
|
+
});
|
|
25
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
26
|
+
const sourceError = (source, message, cause) => new SkillSource.SkillSourceError({ source, message, ...(cause === undefined ? {} : { cause }) });
|
|
27
|
+
const safeInteger = (source, name, value, minimum) => Number.isSafeInteger(value) && value >= minimum
|
|
28
|
+
? Effect.succeed(value)
|
|
29
|
+
: Effect.fail(sourceError(source, `${name} must be a safe integer >= ${minimum}`));
|
|
30
|
+
const request = (url, headers) => {
|
|
31
|
+
let value = HttpClientRequest.get(url);
|
|
32
|
+
for (const [name, header] of Object.entries(headers ?? {}))
|
|
33
|
+
value = HttpClientRequest.setHeader(value, name, header);
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
const fetchBytes = (client, source, url, headers, maxBytes) => client.execute(request(url, headers)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError((error) => sourceError(source, "Hosted skill request failed", error)), Effect.flatMap((response) => response.stream.pipe(Stream.mapError((error) => sourceError(source, "Hosted skill request failed", error)), Stream.runFoldEffect(() => ({ size: 0, chunks: [] }), (state, chunk) => {
|
|
37
|
+
const size = state.size + chunk.byteLength;
|
|
38
|
+
if (size > maxBytes) {
|
|
39
|
+
return Effect.fail(sourceError(source, `Hosted skill response exceeds ${maxBytes} bytes`));
|
|
40
|
+
}
|
|
41
|
+
state.chunks.push(chunk);
|
|
42
|
+
return Effect.succeed({ size, chunks: state.chunks });
|
|
43
|
+
}), Effect.map(({ chunks, size }) => {
|
|
44
|
+
const bytes = new Uint8Array(size);
|
|
45
|
+
let offset = 0;
|
|
46
|
+
for (const chunk of chunks) {
|
|
47
|
+
bytes.set(chunk, offset);
|
|
48
|
+
offset += chunk.byteLength;
|
|
49
|
+
}
|
|
50
|
+
return bytes;
|
|
51
|
+
}))));
|
|
52
|
+
const decodeText = (source, bytes) => Effect.try({
|
|
53
|
+
try: () => decoder.decode(bytes),
|
|
54
|
+
catch: (cause) => sourceError(source, "Hosted skill response is not valid UTF-8", cause),
|
|
55
|
+
});
|
|
56
|
+
const frontmatter = (entry) => ({
|
|
57
|
+
name: entry.name,
|
|
58
|
+
description: entry.description,
|
|
59
|
+
...(entry.whenToUse === undefined ? {} : { whenToUse: entry.whenToUse }),
|
|
60
|
+
...(entry.allowedTools === undefined ? {} : { allowedTools: entry.allowedTools }),
|
|
61
|
+
...(entry.disableModelInvocation === undefined ? {} : { disableModelInvocation: entry.disableModelInvocation }),
|
|
62
|
+
...(entry.userInvocable === undefined ? {} : { userInvocable: entry.userInvocable }),
|
|
63
|
+
...(entry.contextFork === undefined ? {} : { contextFork: entry.contextFork }),
|
|
64
|
+
...(entry.agent === undefined ? {} : { agent: entry.agent }),
|
|
65
|
+
...(entry.model === undefined ? {} : { model: entry.model }),
|
|
66
|
+
...(entry.paths === undefined ? {} : { paths: entry.paths }),
|
|
67
|
+
});
|
|
68
|
+
const sameFrontmatter = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
69
|
+
/** @experimental Validate one safe relative SKILL.md path. */
|
|
70
|
+
export const validateSkillPath = (source, skillPath) => {
|
|
71
|
+
const segments = skillPath.split("/");
|
|
72
|
+
return skillPath.length > 0 &&
|
|
73
|
+
!skillPath.startsWith("/") &&
|
|
74
|
+
!skillPath.includes("\\") &&
|
|
75
|
+
!skillPath.includes("%") &&
|
|
76
|
+
!skillPath.includes("?") &&
|
|
77
|
+
!skillPath.includes("#") &&
|
|
78
|
+
segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..")
|
|
79
|
+
? Effect.succeed(skillPath)
|
|
80
|
+
: Effect.fail(sourceError(source, `Unsafe hosted skill path: ${skillPath}`));
|
|
81
|
+
};
|
|
82
|
+
/** @experimental Resolve a same-origin path beneath a manifest directory. */
|
|
83
|
+
export const resolveRelative = (source, manifestUrl, skillPath) => Effect.gen(function* () {
|
|
84
|
+
yield* validateSkillPath(source, skillPath);
|
|
85
|
+
const manifest = yield* Effect.fromResult(Url.fromString(manifestUrl)).pipe(Effect.mapError((error) => sourceError(source, "Invalid manifest URL", error)));
|
|
86
|
+
const directory = yield* Effect.fromResult(Url.fromString(".", manifest)).pipe(Effect.mapError((error) => sourceError(source, "Invalid manifest directory URL", error)));
|
|
87
|
+
const resolved = yield* Effect.fromResult(Url.fromString(skillPath, directory)).pipe(Effect.mapError((error) => sourceError(source, "Invalid hosted skill URL", error)));
|
|
88
|
+
if (resolved.origin !== manifest.origin || !resolved.pathname.startsWith(directory.pathname)) {
|
|
89
|
+
return yield* Effect.fail(sourceError(source, `Hosted skill path escapes manifest directory: ${skillPath}`));
|
|
90
|
+
}
|
|
91
|
+
return resolved.toString();
|
|
92
|
+
});
|
|
93
|
+
/** @experimental Build a hosted manifest source over Effect HTTP and Crypto services. */
|
|
94
|
+
export const make = (options) => Effect.gen(function* () {
|
|
95
|
+
const client = yield* HttpClient.HttpClient;
|
|
96
|
+
const crypto = yield* Crypto.Crypto;
|
|
97
|
+
const manifestMaxBytes = yield* safeInteger(options.source, "manifestMaxBytes", options.manifestMaxBytes ?? 1024 * 1024, 1);
|
|
98
|
+
const bodyMaxBytes = yield* safeInteger(options.source, "bodyMaxBytes", options.bodyMaxBytes ?? 1024 * 1024, 1);
|
|
99
|
+
const maxSkills = yield* safeInteger(options.source, "maxSkills", options.maxSkills ?? 1_000, 1);
|
|
100
|
+
const descriptionCap = yield* safeInteger(options.source, "descriptionCap", options.descriptionCap ?? SkillSource.DESCRIPTION_CAP, 0);
|
|
101
|
+
const manifestBytes = yield* fetchBytes(client, options.source, options.manifestUrl, options.manifestHeaders, manifestMaxBytes);
|
|
102
|
+
const manifestText = yield* decodeText(options.source, manifestBytes);
|
|
103
|
+
const manifest = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Manifest))(manifestText).pipe(Effect.mapError((error) => sourceError(options.source, "Invalid hosted skill manifest", error)));
|
|
104
|
+
if (manifest.skills.length > maxSkills) {
|
|
105
|
+
return yield* Effect.fail(sourceError(options.source, `Hosted skill manifest exceeds ${maxSkills} skills`));
|
|
106
|
+
}
|
|
107
|
+
const byName = new Map();
|
|
108
|
+
for (const entry of manifest.skills) {
|
|
109
|
+
yield* validateName(options.source, entry.name);
|
|
110
|
+
if (entry.description.length === 0 || entry.description.length > 1_024) {
|
|
111
|
+
return yield* Effect.fail(sourceError(options.source, "Hosted skill description must contain 1-1024 characters"));
|
|
112
|
+
}
|
|
113
|
+
if (!/^[0-9a-f]{64}$/.test(entry.sha256)) {
|
|
114
|
+
return yield* Effect.fail(sourceError(options.source, `Invalid SHA-256 for hosted skill ${entry.name}`));
|
|
115
|
+
}
|
|
116
|
+
if (byName.has(entry.name)) {
|
|
117
|
+
return yield* Effect.fail(sourceError(options.source, `Duplicate hosted skill name: ${entry.name}`));
|
|
118
|
+
}
|
|
119
|
+
const pathSegments = entry.skillPath.split("/");
|
|
120
|
+
if (pathSegments.at(-1) !== "SKILL.md" || pathSegments.at(-2) !== entry.name) {
|
|
121
|
+
return yield* Effect.fail(sourceError(options.source, `Hosted skill path directory must match skill name: ${entry.name}`));
|
|
122
|
+
}
|
|
123
|
+
const skillUrl = yield* options.resolveSkillUrl(entry.skillPath);
|
|
124
|
+
const metadata = frontmatter(entry);
|
|
125
|
+
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
|
|
126
|
+
? decodeText(options.source, bytes)
|
|
127
|
+
: 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)
|
|
128
|
+
? Effect.succeed(document.body)
|
|
129
|
+
: Effect.fail(sourceError(options.source, `Frontmatter mismatch for hosted skill ${entry.name}`))))));
|
|
130
|
+
const tools = options.toolsBySkill !== undefined && Object.hasOwn(options.toolsBySkill, entry.name)
|
|
131
|
+
? (options.toolsBySkill[entry.name] ?? [])
|
|
132
|
+
: [];
|
|
133
|
+
byName.set(entry.name, {
|
|
134
|
+
frontmatter: metadata,
|
|
135
|
+
listing: SkillSource.makeListing(metadata, descriptionCap),
|
|
136
|
+
body,
|
|
137
|
+
tools,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const skills = [...byName.values()];
|
|
141
|
+
return {
|
|
142
|
+
all: Effect.succeed(skills),
|
|
143
|
+
get: (name) => Effect.succeed(byName.get(name)),
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
/** @experimental One-source hosted catalog layer. */
|
|
147
|
+
export const layer = (options) => Layer.effect(SkillSource.SkillSource, make(options).pipe(Effect.map(SkillSource.SkillSource.of)));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Crypto } from "effect";
|
|
2
|
+
import { HttpClient } from "effect/unstable/http";
|
|
3
|
+
import { SkillSource } from "@batonfx/core";
|
|
4
|
+
import { type Limits } from "./hosted-catalog.js";
|
|
5
|
+
/** @experimental Generic HTTP skill catalog options. */
|
|
6
|
+
export interface Options extends Limits {
|
|
7
|
+
readonly manifestUrl: string;
|
|
8
|
+
readonly source?: string;
|
|
9
|
+
}
|
|
10
|
+
/** @experimental Build a generic HTTP catalog source. */
|
|
11
|
+
export declare const make: (options: Options) => SkillSource.Source<HttpClient.HttpClient | Crypto.Crypto>;
|
|
12
|
+
/** @experimental Build a generic HTTP catalog layer. */
|
|
13
|
+
export declare const layer: (options: Options) => import("effect/Layer").Layer<SkillSource.SkillSource, SkillSource.SkillSourceError, HttpClient.HttpClient | Crypto.Crypto>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Crypto, Effect } from "effect";
|
|
2
|
+
import { HttpClient, Url } from "effect/unstable/http";
|
|
3
|
+
import { SkillSource } from "@batonfx/core";
|
|
4
|
+
import { make as makeHostedCatalog, resolveRelative } from "./hosted-catalog.js";
|
|
5
|
+
const invalidUrl = (cause) => new SkillSource.SkillSourceError({ source: "http-skill-catalog", message: "Invalid manifest URL", cause });
|
|
6
|
+
/** @experimental Build a generic HTTP catalog source. */
|
|
7
|
+
export const make = (options) => {
|
|
8
|
+
return Effect.gen(function* () {
|
|
9
|
+
const parsed = yield* Effect.fromResult(Url.fromString(options.manifestUrl)).pipe(Effect.mapError(invalidUrl));
|
|
10
|
+
const source = options.source ?? `${parsed.origin}${parsed.pathname}`;
|
|
11
|
+
return yield* makeHostedCatalog({
|
|
12
|
+
...options,
|
|
13
|
+
source,
|
|
14
|
+
resolveSkillUrl: (skillPath) => resolveRelative(source, options.manifestUrl, skillPath),
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
/** @experimental Build a generic HTTP catalog layer. */
|
|
19
|
+
export const layer = (options) => SkillSource.layer([make(options)]);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * as GitHubCatalog from "./github-catalog.js";
|
|
2
|
+
export * as HostedCatalog from "./hosted-catalog.js";
|
|
3
|
+
export * as HttpCatalog from "./http-catalog.js";
|
|
4
|
+
export * as InstructionFiles from "./instructions-files.js";
|
|
5
|
+
export * as S3Catalog from "./s3-catalog.js";
|
|
6
|
+
export * as SkillLoader from "./skill-loader.js";
|