@skillerr/core 0.6.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 +38 -0
- package/dist/compile.d.ts +52 -0
- package/dist/compile.js +977 -0
- package/dist/hash.d.ts +18 -0
- package/dist/hash.js +122 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +8 -0
- package/dist/migrate.d.ts +8 -0
- package/dist/migrate.js +116 -0
- package/dist/mint.d.ts +73 -0
- package/dist/mint.js +467 -0
- package/dist/pack.d.ts +20 -0
- package/dist/pack.js +191 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +22 -0
- package/dist/validate.d.ts +36 -0
- package/dist/validate.js +274 -0
- package/package.json +58 -0
package/dist/paths.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const UNSAFE = /(^\/)|(\.\.)|(^$)/;
|
|
2
|
+
export function normalizePath(path) {
|
|
3
|
+
const nfc = path.normalize("NFC").replace(/\\/g, "/");
|
|
4
|
+
if (nfc.includes("\0"))
|
|
5
|
+
throw new Error(`Null byte in path: ${path}`);
|
|
6
|
+
if (UNSAFE.test(nfc) || nfc.split("/").some((s) => s === "" || s === "." || s === "..")) {
|
|
7
|
+
throw new Error(`Unsafe path: ${path}`);
|
|
8
|
+
}
|
|
9
|
+
return nfc;
|
|
10
|
+
}
|
|
11
|
+
export function assertSafePaths(paths) {
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
for (const p of paths) {
|
|
14
|
+
const n = normalizePath(p);
|
|
15
|
+
if (seen.has(n))
|
|
16
|
+
throw new Error(`Duplicate path: ${n}`);
|
|
17
|
+
seen.add(n);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export const MAX_ENTRIES = 10_000;
|
|
21
|
+
export const MAX_UNCOMPRESSED_BYTES = 64 * 1024 * 1024;
|
|
22
|
+
export const MAX_COMPRESSION_RATIO = 100;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SkillManifest, Workflow } from "@skillerr/protocol";
|
|
2
|
+
export interface ValidationIssue {
|
|
3
|
+
severity: "error" | "warning";
|
|
4
|
+
code: string;
|
|
5
|
+
message: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ValidationResult {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
issues: ValidationIssue[];
|
|
11
|
+
manifest?: SkillManifest;
|
|
12
|
+
workflow?: Workflow;
|
|
13
|
+
}
|
|
14
|
+
export declare function validateManifestShape(manifest: SkillManifest): ValidationIssue[];
|
|
15
|
+
export declare function validateWorkflowShape(workflow: Workflow, entrypoint: string): ValidationIssue[];
|
|
16
|
+
export declare function validatePackageBytes(archive: Uint8Array): ValidationResult;
|
|
17
|
+
export declare function inspectSkill(archive: Uint8Array): {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
summary: {
|
|
20
|
+
id: string;
|
|
21
|
+
version: string;
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
intent?: string;
|
|
25
|
+
inputs: string[];
|
|
26
|
+
permissions: string[];
|
|
27
|
+
capabilities: string[];
|
|
28
|
+
package_digest: string;
|
|
29
|
+
sealed_manifest_digest?: string;
|
|
30
|
+
mint_status?: string;
|
|
31
|
+
needs_human_review?: boolean;
|
|
32
|
+
trust_label?: string;
|
|
33
|
+
trust_state?: string;
|
|
34
|
+
};
|
|
35
|
+
issues: ValidationIssue[];
|
|
36
|
+
};
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { assessSkillContract, CONTAINER_VERSION, PROTOCOL_VERSION, WORKFLOW_DIALECT_VERSION, } from "@skillerr/protocol";
|
|
2
|
+
import { packageDigestFromContent, sha256Digest } from "./hash.js";
|
|
3
|
+
import { unpackSkill } from "./pack.js";
|
|
4
|
+
export function validateManifestShape(manifest) {
|
|
5
|
+
const issues = [];
|
|
6
|
+
if (manifest.kind !== "dot-skill") {
|
|
7
|
+
issues.push({ severity: "error", code: "kind", message: "kind must be dot-skill" });
|
|
8
|
+
}
|
|
9
|
+
if (!manifest.id)
|
|
10
|
+
issues.push({ severity: "error", code: "id", message: "id required" });
|
|
11
|
+
if (!manifest.version)
|
|
12
|
+
issues.push({ severity: "error", code: "version", message: "version required" });
|
|
13
|
+
if (!manifest.title)
|
|
14
|
+
issues.push({ severity: "error", code: "title", message: "title required" });
|
|
15
|
+
if (!manifest.description)
|
|
16
|
+
issues.push({
|
|
17
|
+
severity: "error",
|
|
18
|
+
code: "description",
|
|
19
|
+
message: "description required",
|
|
20
|
+
});
|
|
21
|
+
if (!manifest.entrypoint)
|
|
22
|
+
issues.push({
|
|
23
|
+
severity: "error",
|
|
24
|
+
code: "entrypoint",
|
|
25
|
+
message: "entrypoint required",
|
|
26
|
+
});
|
|
27
|
+
if (manifest.container_version !== CONTAINER_VERSION) {
|
|
28
|
+
issues.push({
|
|
29
|
+
severity: "warning",
|
|
30
|
+
code: "container_version",
|
|
31
|
+
message: `Unexpected container_version ${manifest.container_version}`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (manifest.protocol_version !== PROTOCOL_VERSION) {
|
|
35
|
+
issues.push({
|
|
36
|
+
severity: "error",
|
|
37
|
+
code: "protocol_version",
|
|
38
|
+
message: `Unsupported protocol_version ${manifest.protocol_version}; expected ${PROTOCOL_VERSION}`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (manifest.compile_profile === "release") {
|
|
42
|
+
if (!manifest.contract) {
|
|
43
|
+
issues.push({
|
|
44
|
+
severity: "error",
|
|
45
|
+
code: "release_contract_missing",
|
|
46
|
+
message: "Release packages require a native 0.5 authoring contract",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
for (const issue of assessSkillContract(manifest.contract, "release").issues) {
|
|
51
|
+
issues.push({
|
|
52
|
+
severity: "error",
|
|
53
|
+
code: `contract_${issue.code}`,
|
|
54
|
+
message: `${issue.field}: ${issue.message}`,
|
|
55
|
+
path: issue.field,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (manifest.mint?.mint_status === "minted") {
|
|
61
|
+
if (manifest.compile_profile !== "release") {
|
|
62
|
+
issues.push({
|
|
63
|
+
severity: "error",
|
|
64
|
+
code: "minted_profile",
|
|
65
|
+
message: "Minted packages must use compile_profile=release",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (!manifest.completeness?.complete) {
|
|
69
|
+
issues.push({
|
|
70
|
+
severity: "error",
|
|
71
|
+
code: "minted_incomplete",
|
|
72
|
+
message: "Minted packages require a complete release report",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const input of manifest.inputs ?? []) {
|
|
77
|
+
if (input.sensitivity === "secret" && input.examples?.length) {
|
|
78
|
+
issues.push({
|
|
79
|
+
severity: "error",
|
|
80
|
+
code: "secret_examples",
|
|
81
|
+
message: `Input ${input.name} is secret but includes examples`,
|
|
82
|
+
path: input.name,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return issues;
|
|
87
|
+
}
|
|
88
|
+
export function validateWorkflowShape(workflow, entrypoint) {
|
|
89
|
+
const issues = [];
|
|
90
|
+
if (workflow.kind !== "workflow") {
|
|
91
|
+
issues.push({ severity: "error", code: "workflow_kind", message: "kind must be workflow" });
|
|
92
|
+
}
|
|
93
|
+
if (workflow.dialect_version !== WORKFLOW_DIALECT_VERSION) {
|
|
94
|
+
issues.push({
|
|
95
|
+
severity: "warning",
|
|
96
|
+
code: "dialect",
|
|
97
|
+
message: `Unexpected dialect_version ${workflow.dialect_version}`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const ids = new Set(workflow.steps.map((s) => s.id));
|
|
101
|
+
if (!ids.has(entrypoint)) {
|
|
102
|
+
issues.push({
|
|
103
|
+
severity: "error",
|
|
104
|
+
code: "entrypoint_missing",
|
|
105
|
+
message: `Entrypoint ${entrypoint} not in steps`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
for (const step of workflow.steps) {
|
|
109
|
+
if (!step.id || !step.kind) {
|
|
110
|
+
issues.push({
|
|
111
|
+
severity: "error",
|
|
112
|
+
code: "step",
|
|
113
|
+
message: "Each step needs id and kind",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const refs = [
|
|
117
|
+
...(typeof step.next === "string" ? [step.next] : step.next ?? []),
|
|
118
|
+
...(step.on_fail ? [step.on_fail] : []),
|
|
119
|
+
...(step.kind === "branch"
|
|
120
|
+
? [...step.cases.map((branch) => branch.goto), ...(step.else ? [step.else] : [])]
|
|
121
|
+
: []),
|
|
122
|
+
];
|
|
123
|
+
for (const ref of refs) {
|
|
124
|
+
if (!ids.has(ref)) {
|
|
125
|
+
issues.push({
|
|
126
|
+
severity: "error",
|
|
127
|
+
code: "step_reference_missing",
|
|
128
|
+
message: `Step ${step.id} references missing step ${ref}`,
|
|
129
|
+
path: step.id,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return issues;
|
|
135
|
+
}
|
|
136
|
+
export function validatePackageBytes(archive) {
|
|
137
|
+
const issues = [];
|
|
138
|
+
let unpacked;
|
|
139
|
+
try {
|
|
140
|
+
unpacked = unpackSkill(archive);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
issues: [
|
|
146
|
+
{
|
|
147
|
+
severity: "error",
|
|
148
|
+
code: "unpack",
|
|
149
|
+
message: e instanceof Error ? e.message : String(e),
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
issues.push(...validateManifestShape(unpacked.manifest));
|
|
155
|
+
issues.push(...validateWorkflowShape(unpacked.workflow, unpacked.manifest.entrypoint));
|
|
156
|
+
const computed = [];
|
|
157
|
+
for (const [path, data] of Object.entries(unpacked.files)) {
|
|
158
|
+
if (path === "skill.json" || path.startsWith("signatures/"))
|
|
159
|
+
continue;
|
|
160
|
+
const digest = sha256Digest(data);
|
|
161
|
+
computed.push({ path, digest });
|
|
162
|
+
const listed = unpacked.manifest.content.find((c) => c.path === path);
|
|
163
|
+
if (!listed) {
|
|
164
|
+
issues.push({
|
|
165
|
+
severity: "error",
|
|
166
|
+
code: "missing_content_entry",
|
|
167
|
+
message: `File ${path} not listed in manifest.content`,
|
|
168
|
+
path,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else if (listed.digest !== digest) {
|
|
172
|
+
issues.push({
|
|
173
|
+
severity: "error",
|
|
174
|
+
code: "digest_mismatch",
|
|
175
|
+
message: `Digest mismatch for ${path}`,
|
|
176
|
+
path,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const entry of unpacked.manifest.content) {
|
|
181
|
+
if (!unpacked.files[entry.path]) {
|
|
182
|
+
issues.push({
|
|
183
|
+
severity: "error",
|
|
184
|
+
code: "missing_file",
|
|
185
|
+
message: `Manifest lists missing file ${entry.path}`,
|
|
186
|
+
path: entry.path,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const expectedPkg = packageDigestFromContent(computed);
|
|
191
|
+
if (unpacked.manifest.package_digest !== expectedPkg) {
|
|
192
|
+
issues.push({
|
|
193
|
+
severity: "error",
|
|
194
|
+
code: "package_digest",
|
|
195
|
+
message: "package_digest does not match content index",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (unpacked.manifest.policy.require_signatures) {
|
|
199
|
+
const sigs = Object.keys(unpacked.files).filter((p) => p.startsWith("signatures/"));
|
|
200
|
+
if (sigs.length === 0) {
|
|
201
|
+
issues.push({
|
|
202
|
+
severity: "error",
|
|
203
|
+
code: "signatures_required",
|
|
204
|
+
message: "Policy requires signatures but none present",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const ok = !issues.some((i) => i.severity === "error");
|
|
209
|
+
return {
|
|
210
|
+
ok,
|
|
211
|
+
issues,
|
|
212
|
+
manifest: unpacked.manifest,
|
|
213
|
+
workflow: unpacked.workflow,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export function inspectSkill(archive) {
|
|
217
|
+
const result = validatePackageBytes(archive);
|
|
218
|
+
if (!result.manifest) {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
summary: {
|
|
222
|
+
id: "",
|
|
223
|
+
version: "",
|
|
224
|
+
title: "",
|
|
225
|
+
description: "",
|
|
226
|
+
inputs: [],
|
|
227
|
+
permissions: [],
|
|
228
|
+
capabilities: [],
|
|
229
|
+
package_digest: "",
|
|
230
|
+
trust_label: "INVALID",
|
|
231
|
+
trust_state: "untrusted",
|
|
232
|
+
},
|
|
233
|
+
issues: result.issues,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const m = result.manifest;
|
|
237
|
+
const mint_status = m.mint?.mint_status ?? "draft";
|
|
238
|
+
const signed = Boolean((result.manifest &&
|
|
239
|
+
Object.keys(
|
|
240
|
+
// signatures live outside validate result — infer from mint + attestation_digest
|
|
241
|
+
m.attestation_digest ? { signed: true } : {}).length) ||
|
|
242
|
+
mint_status === "minted");
|
|
243
|
+
// Lightweight label without full TrustView import cycle; CLI --trust uses inspectTrustView.
|
|
244
|
+
let trust_label = "UNSIGNED / OPEN — untrusted";
|
|
245
|
+
let trust_state = "untrusted";
|
|
246
|
+
if (mint_status === "minted" && m.attestation_digest) {
|
|
247
|
+
trust_label = "SEALED — use skill inspect --trust for issuer/host details";
|
|
248
|
+
trust_state = "self_reported";
|
|
249
|
+
}
|
|
250
|
+
else if (!signed || mint_status === "draft") {
|
|
251
|
+
trust_label = "UNSIGNED / OPEN — untrusted";
|
|
252
|
+
trust_state = "untrusted";
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
ok: result.ok,
|
|
256
|
+
summary: {
|
|
257
|
+
id: m.id,
|
|
258
|
+
version: m.version,
|
|
259
|
+
title: m.title,
|
|
260
|
+
description: m.description,
|
|
261
|
+
intent: m.intent,
|
|
262
|
+
inputs: m.inputs.filter((i) => i.required).map((i) => i.name),
|
|
263
|
+
permissions: m.permissions.map((p) => p.side_effect_class),
|
|
264
|
+
capabilities: m.capabilities.map((c) => c.name),
|
|
265
|
+
package_digest: m.package_digest,
|
|
266
|
+
sealed_manifest_digest: m.sealed_manifest_digest,
|
|
267
|
+
mint_status,
|
|
268
|
+
needs_human_review: m.needs_human_review,
|
|
269
|
+
trust_label,
|
|
270
|
+
trust_state,
|
|
271
|
+
},
|
|
272
|
+
issues: result.issues,
|
|
273
|
+
};
|
|
274
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skillerr/core",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Compile, pack, validate, and mint portable .skill packages",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Bharat Dudeja",
|
|
8
|
+
"url": "https://github.com/bharatdudeja13-cmd"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"skill",
|
|
12
|
+
".skill",
|
|
13
|
+
"ai-agents",
|
|
14
|
+
"agent-skills",
|
|
15
|
+
"compile",
|
|
16
|
+
"mint"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"provenance": true
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/dot-skill/dot-skill.git",
|
|
28
|
+
"directory": "packages/core"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://skillerr.com",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/dot-skill/dot-skill/issues"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"import": "./dist/index.js",
|
|
42
|
+
"types": "./dist/index.d.ts"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rm -rf dist && tsc",
|
|
47
|
+
"prepack": "npm run build",
|
|
48
|
+
"clean": "rm -rf dist"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@skillerr/protocol": "^0.6.0",
|
|
52
|
+
"fflate": "^0.8.2"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^26.1.1",
|
|
56
|
+
"typescript": "^5.4.5"
|
|
57
|
+
}
|
|
58
|
+
}
|