@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/hash.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SealedManifestClaims, SkillManifest } from "@skillerr/protocol";
|
|
2
|
+
/** RFC 8785-inspired JSON Canonicalization for I-JSON-compatible objects. */
|
|
3
|
+
export declare function canonicalize(value: unknown): string;
|
|
4
|
+
export declare function sha256Hex(data: string | Uint8Array): string;
|
|
5
|
+
export declare function sha256Digest(data: string | Uint8Array): string;
|
|
6
|
+
export declare function packageDigestFromContent(content: Array<{
|
|
7
|
+
path: string;
|
|
8
|
+
digest: string;
|
|
9
|
+
}>): string;
|
|
10
|
+
/** Public development HMAC key — forgeable; never production trust. */
|
|
11
|
+
export declare const PUBLIC_DEV_MINT_KEY = "dot-skill-dev-mint-key";
|
|
12
|
+
export declare const PUBLIC_DEV_MINT_KEY_ID = "dot-skill-dev-mint-key";
|
|
13
|
+
/**
|
|
14
|
+
* Build the claim set covered by the creation seal.
|
|
15
|
+
* Binds identity, intent, permissions, policy, capabilities, inputs, and content digests.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildSealedManifestClaims(manifest: SkillManifest): SealedManifestClaims;
|
|
18
|
+
export declare function sealedManifestDigest(manifest: SkillManifest): string;
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** RFC 8785-inspired JSON Canonicalization for I-JSON-compatible objects. */
|
|
3
|
+
export function canonicalize(value) {
|
|
4
|
+
return serialize(value);
|
|
5
|
+
}
|
|
6
|
+
function serialize(value) {
|
|
7
|
+
if (value === null)
|
|
8
|
+
return "null";
|
|
9
|
+
if (typeof value === "boolean")
|
|
10
|
+
return value ? "true" : "false";
|
|
11
|
+
if (typeof value === "number") {
|
|
12
|
+
if (!Number.isFinite(value)) {
|
|
13
|
+
throw new Error("JCS forbids non-finite numbers");
|
|
14
|
+
}
|
|
15
|
+
return JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "string")
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return `[${value.map((v) => serialize(v)).join(",")}]`;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "object") {
|
|
23
|
+
const obj = value;
|
|
24
|
+
const keys = Object.keys(obj).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
25
|
+
const parts = [];
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
const v = obj[key];
|
|
28
|
+
if (v === undefined)
|
|
29
|
+
continue;
|
|
30
|
+
parts.push(`${JSON.stringify(key)}:${serialize(v)}`);
|
|
31
|
+
}
|
|
32
|
+
return `{${parts.join(",")}}`;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unsupported JSON value type: ${typeof value}`);
|
|
35
|
+
}
|
|
36
|
+
export function sha256Hex(data) {
|
|
37
|
+
const hash = createHash("sha256");
|
|
38
|
+
hash.update(typeof data === "string" ? Buffer.from(data, "utf8") : data);
|
|
39
|
+
return hash.digest("hex");
|
|
40
|
+
}
|
|
41
|
+
export function sha256Digest(data) {
|
|
42
|
+
return `sha256:${sha256Hex(data)}`;
|
|
43
|
+
}
|
|
44
|
+
export function packageDigestFromContent(content) {
|
|
45
|
+
const paths = {};
|
|
46
|
+
for (const entry of [...content].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
47
|
+
if (entry.path.startsWith("signatures/"))
|
|
48
|
+
continue;
|
|
49
|
+
paths[entry.path] = entry.digest;
|
|
50
|
+
}
|
|
51
|
+
return sha256Digest(canonicalize({ paths }));
|
|
52
|
+
}
|
|
53
|
+
/** Public development HMAC key — forgeable; never production trust. */
|
|
54
|
+
export const PUBLIC_DEV_MINT_KEY = "dot-skill-dev-mint-key";
|
|
55
|
+
export const PUBLIC_DEV_MINT_KEY_ID = "dot-skill-dev-mint-key";
|
|
56
|
+
/**
|
|
57
|
+
* Build the claim set covered by the creation seal.
|
|
58
|
+
* Binds identity, intent, permissions, policy, capabilities, inputs, and content digests.
|
|
59
|
+
*/
|
|
60
|
+
export function buildSealedManifestClaims(manifest) {
|
|
61
|
+
const claims = {
|
|
62
|
+
id: manifest.id,
|
|
63
|
+
version: manifest.version,
|
|
64
|
+
title: manifest.title,
|
|
65
|
+
intent: manifest.intent,
|
|
66
|
+
description: manifest.description,
|
|
67
|
+
package_digest: manifest.package_digest,
|
|
68
|
+
permissions: [...manifest.permissions]
|
|
69
|
+
.map((p) => ({
|
|
70
|
+
side_effect_class: p.side_effect_class,
|
|
71
|
+
description: p.description,
|
|
72
|
+
paths: p.paths,
|
|
73
|
+
hosts: p.hosts,
|
|
74
|
+
requires_consent: p.requires_consent,
|
|
75
|
+
}))
|
|
76
|
+
.sort((a, b) => `${a.side_effect_class}:${a.description}`.localeCompare(`${b.side_effect_class}:${b.description}`)),
|
|
77
|
+
policy: {
|
|
78
|
+
require_signatures: manifest.policy.require_signatures,
|
|
79
|
+
require_minted: manifest.policy.require_minted,
|
|
80
|
+
require_anchor: manifest.policy.require_anchor,
|
|
81
|
+
allow_network: manifest.policy.allow_network,
|
|
82
|
+
filesystem_roots: manifest.policy.filesystem_roots
|
|
83
|
+
? [...manifest.policy.filesystem_roots].sort()
|
|
84
|
+
: undefined,
|
|
85
|
+
consent_for: [...manifest.policy.consent_for].sort(),
|
|
86
|
+
trust_profile: manifest.policy.trust_profile,
|
|
87
|
+
max_tool_calls: manifest.policy.max_tool_calls,
|
|
88
|
+
max_runtime_ms: manifest.policy.max_runtime_ms,
|
|
89
|
+
fail_on_unsupported_step: manifest.policy.fail_on_unsupported_step,
|
|
90
|
+
},
|
|
91
|
+
capabilities: [...manifest.capabilities]
|
|
92
|
+
.map((c) => ({
|
|
93
|
+
name: c.name,
|
|
94
|
+
side_effect_class: c.side_effect_class,
|
|
95
|
+
required: c.required,
|
|
96
|
+
}))
|
|
97
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
98
|
+
inputs: [...manifest.inputs]
|
|
99
|
+
.map((i) => ({
|
|
100
|
+
name: i.name,
|
|
101
|
+
sensitivity: i.sensitivity,
|
|
102
|
+
required: i.required,
|
|
103
|
+
source: i.source,
|
|
104
|
+
}))
|
|
105
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
106
|
+
content: [...manifest.content]
|
|
107
|
+
.map((c) => ({ path: c.path, digest: c.digest, media_type: c.media_type, bytes: c.bytes }))
|
|
108
|
+
.sort((a, b) => a.path.localeCompare(b.path)),
|
|
109
|
+
};
|
|
110
|
+
if (manifest.contract) {
|
|
111
|
+
claims.contract = {
|
|
112
|
+
title: manifest.contract.title,
|
|
113
|
+
intent: manifest.contract.intent,
|
|
114
|
+
skill_kind: manifest.contract.skill_kind,
|
|
115
|
+
sensitivity: manifest.contract.sensitivity,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return claims;
|
|
119
|
+
}
|
|
120
|
+
export function sealedManifestDigest(manifest) {
|
|
121
|
+
return sha256Digest(canonicalize(buildSealedManifestClaims(manifest)));
|
|
122
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @skillerr/core — pack, unpack, validate, mint, compile .skill packages. */
|
|
2
|
+
export { canonicalize, sha256Hex, sha256Digest, packageDigestFromContent, buildSealedManifestClaims, sealedManifestDigest, PUBLIC_DEV_MINT_KEY, PUBLIC_DEV_MINT_KEY_ID, } from "./hash.js";
|
|
3
|
+
export { normalizePath, assertSafePaths, MAX_ENTRIES, MAX_UNCOMPRESSED_BYTES, MAX_COMPRESSION_RATIO, } from "./paths.js";
|
|
4
|
+
export { buildFileMap, finalizeManifest, packSkill, unpackSkill, } from "./pack.js";
|
|
5
|
+
export type { PackOptions, UnpackResult } from "./pack.js";
|
|
6
|
+
export { validateManifestShape, validateWorkflowShape, validatePackageBytes, inspectSkill, } from "./validate.js";
|
|
7
|
+
export type { ValidationIssue, ValidationResult } from "./validate.js";
|
|
8
|
+
export { migrateLegacySkill, toSkillMdAdapter } from "./migrate.js";
|
|
9
|
+
export { mintSkillPackage, addPermanenceAnchor, verifyMintTrust, inspectTrustView, } from "./mint.js";
|
|
10
|
+
export type { MintOptions, VerifyMintTrustOptions } from "./mint.js";
|
|
11
|
+
export { compileSkillSource, compileRecipeToSkill, approveCompilation, assessCompleteness, redactSecrets, CompileRefusalError, } from "./compile.js";
|
|
12
|
+
export type { CompileOptions, CompileResult } from "./compile.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** @skillerr/core — pack, unpack, validate, mint, compile .skill packages. */
|
|
2
|
+
export { canonicalize, sha256Hex, sha256Digest, packageDigestFromContent, buildSealedManifestClaims, sealedManifestDigest, PUBLIC_DEV_MINT_KEY, PUBLIC_DEV_MINT_KEY_ID, } from "./hash.js";
|
|
3
|
+
export { normalizePath, assertSafePaths, MAX_ENTRIES, MAX_UNCOMPRESSED_BYTES, MAX_COMPRESSION_RATIO, } from "./paths.js";
|
|
4
|
+
export { buildFileMap, finalizeManifest, packSkill, unpackSkill, } from "./pack.js";
|
|
5
|
+
export { validateManifestShape, validateWorkflowShape, validatePackageBytes, inspectSkill, } from "./validate.js";
|
|
6
|
+
export { migrateLegacySkill, toSkillMdAdapter } from "./migrate.js";
|
|
7
|
+
export { mintSkillPackage, addPermanenceAnchor, verifyMintTrust, inspectTrustView, } from "./mint.js";
|
|
8
|
+
export { compileSkillSource, compileRecipeToSkill, approveCompilation, assessCompleteness, redactSecrets, CompileRefusalError, } from "./compile.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Skill } from "@skillerr/protocol";
|
|
2
|
+
import { type SkillPackageFiles } from "@skillerr/protocol";
|
|
3
|
+
/** Convert legacy flat skill JSON into a draft `.skill` package (bytes). */
|
|
4
|
+
export declare function migrateLegacySkill(legacy: Skill): {
|
|
5
|
+
packageBytes: Uint8Array;
|
|
6
|
+
files: SkillPackageFiles;
|
|
7
|
+
};
|
|
8
|
+
export declare function toSkillMdAdapter(pkg: SkillPackageFiles): string;
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { DEFAULT_SKILL_POLICY, CONTAINER_VERSION, PROTOCOL_VERSION, WORKFLOW_DIALECT_VERSION, } from "@skillerr/protocol";
|
|
2
|
+
import { packSkill } from "./pack.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
/** Convert legacy flat skill JSON into a draft `.skill` package (bytes). */
|
|
5
|
+
export function migrateLegacySkill(legacy) {
|
|
6
|
+
const id = legacy.id || `skl_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
7
|
+
const knowledgeId = "k_legacy_body";
|
|
8
|
+
const files = {
|
|
9
|
+
manifest: {
|
|
10
|
+
kind: "dot-skill",
|
|
11
|
+
id,
|
|
12
|
+
version: legacy.version || "0.1.0",
|
|
13
|
+
title: legacy.title || "Migrated skill",
|
|
14
|
+
description: "Draft migrated from legacy flat skill format. Needs human review before release.",
|
|
15
|
+
container_version: CONTAINER_VERSION,
|
|
16
|
+
protocol_version: PROTOCOL_VERSION,
|
|
17
|
+
entrypoint: "s_instruct",
|
|
18
|
+
inputs: [],
|
|
19
|
+
outputs: [
|
|
20
|
+
{
|
|
21
|
+
name: "result",
|
|
22
|
+
schema: { type: "string" },
|
|
23
|
+
required: false,
|
|
24
|
+
description: "Unstructured result from legacy instruct skill",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
capabilities: [],
|
|
28
|
+
permissions: [],
|
|
29
|
+
policy: { ...DEFAULT_SKILL_POLICY },
|
|
30
|
+
content: [],
|
|
31
|
+
package_digest: "sha256:" + "0".repeat(64),
|
|
32
|
+
provenance_mode: "proof_only",
|
|
33
|
+
legacy: true,
|
|
34
|
+
needs_human_review: true,
|
|
35
|
+
},
|
|
36
|
+
workflow: {
|
|
37
|
+
kind: "workflow",
|
|
38
|
+
dialect_version: WORKFLOW_DIALECT_VERSION,
|
|
39
|
+
entrypoint: "s_instruct",
|
|
40
|
+
steps: [
|
|
41
|
+
{
|
|
42
|
+
id: "s_instruct",
|
|
43
|
+
kind: "instruct",
|
|
44
|
+
title: "Legacy body",
|
|
45
|
+
text: legacy.body,
|
|
46
|
+
next: "s_emit",
|
|
47
|
+
provenance: [{ kind: "legacy_skill", id: legacy.id }],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "s_emit",
|
|
51
|
+
kind: "emit",
|
|
52
|
+
output: "result",
|
|
53
|
+
from: "s_instruct",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
knowledge: [
|
|
58
|
+
{
|
|
59
|
+
kind: "knowledge",
|
|
60
|
+
id: knowledgeId,
|
|
61
|
+
type: "reference",
|
|
62
|
+
title: "Legacy skill body",
|
|
63
|
+
body: legacy.body,
|
|
64
|
+
fidelity: "exact",
|
|
65
|
+
provenance: [{ kind: "legacy_skill", id: legacy.id }],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
provenance: {
|
|
69
|
+
proof: {
|
|
70
|
+
sources: legacy.sources,
|
|
71
|
+
exported_at: legacy.exported_at,
|
|
72
|
+
legacy_protocol_version: legacy.source_protocol_version,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const packageBytes = packSkill(files);
|
|
77
|
+
return { packageBytes, files };
|
|
78
|
+
}
|
|
79
|
+
export function toSkillMdAdapter(pkg) {
|
|
80
|
+
const m = pkg.manifest;
|
|
81
|
+
const lines = [
|
|
82
|
+
"---",
|
|
83
|
+
`name: ${m.id.replace(/[^a-z0-9-]/gi, "-").toLowerCase().slice(0, 64)}`,
|
|
84
|
+
`description: ${m.description.slice(0, 1024)}`,
|
|
85
|
+
"metadata:",
|
|
86
|
+
" dot_skill_authoritative: true",
|
|
87
|
+
` skill_id: ${m.id}`,
|
|
88
|
+
` skill_version: ${m.version}`,
|
|
89
|
+
` package_digest: ${m.package_digest}`,
|
|
90
|
+
"---",
|
|
91
|
+
"",
|
|
92
|
+
`# ${m.title}`,
|
|
93
|
+
"",
|
|
94
|
+
m.intent ?? m.description,
|
|
95
|
+
"",
|
|
96
|
+
"## Inputs",
|
|
97
|
+
"",
|
|
98
|
+
];
|
|
99
|
+
for (const input of m.inputs) {
|
|
100
|
+
lines.push(`- **${input.name}** (${input.source}, ${input.required ? "required" : "optional"}): ${input.description}`);
|
|
101
|
+
}
|
|
102
|
+
if (m.inputs.length === 0)
|
|
103
|
+
lines.push("- (none)");
|
|
104
|
+
lines.push("", "## Workflow", "");
|
|
105
|
+
for (const step of pkg.workflow.steps) {
|
|
106
|
+
lines.push(`### ${step.id} (\`${step.kind}\`)`);
|
|
107
|
+
if ("text" in step && step.text)
|
|
108
|
+
lines.push(step.text, "");
|
|
109
|
+
if ("template" in step && step.template)
|
|
110
|
+
lines.push(step.template, "");
|
|
111
|
+
if ("prompt" in step && step.prompt)
|
|
112
|
+
lines.push(step.prompt, "");
|
|
113
|
+
}
|
|
114
|
+
lines.push("", "> This SKILL.md is a **lossy adapter**. The authoritative artifact is the `.skill` package.");
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
package/dist/mint.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { CreationAttestation, HostClaimBinding, PermanenceAnchor, SkillPackageFiles, TrustProfile, TrustState, TrustView } from "@skillerr/protocol";
|
|
2
|
+
import { type ValidationIssue } from "./validate.js";
|
|
3
|
+
export interface MintOptions {
|
|
4
|
+
host: string;
|
|
5
|
+
provider?: string;
|
|
6
|
+
agent_runtime?: string;
|
|
7
|
+
agent_version?: string;
|
|
8
|
+
key_id?: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
deployment?: "local" | "hosted" | "hybrid" | "unknown";
|
|
11
|
+
endpoint?: string;
|
|
12
|
+
actors?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* HMAC-style digest seal.
|
|
15
|
+
* Omit / use the public constant only for local development — never production trust.
|
|
16
|
+
*/
|
|
17
|
+
issuer_secret?: string;
|
|
18
|
+
policy_profile?: TrustProfile;
|
|
19
|
+
/**
|
|
20
|
+
* Evidence that mint was invoked from an agent runtime path (not a bare human shell
|
|
21
|
+
* that only exported SKILL_HOST). Markers are still locally spoofable; residual risk
|
|
22
|
+
* for local LLMs remains.
|
|
23
|
+
*/
|
|
24
|
+
agent_runtime_evidence?: {
|
|
25
|
+
markers?: string[];
|
|
26
|
+
session_id?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Only set when a non-public issuer key authenticates the host/model claim.
|
|
30
|
+
* Default is always self_reported.
|
|
31
|
+
*/
|
|
32
|
+
host_claim_binding?: HostClaimBinding;
|
|
33
|
+
/** Env snapshot for marker detection (defaults to process.env). */
|
|
34
|
+
env?: Record<string, string | undefined>;
|
|
35
|
+
}
|
|
36
|
+
export interface VerifyMintTrustOptions {
|
|
37
|
+
issuer_secret?: string;
|
|
38
|
+
/**
|
|
39
|
+
* When true, public-dev HMAC may pass structural verification as trust_state=development.
|
|
40
|
+
* Production execute paths must leave this false (fail closed).
|
|
41
|
+
*/
|
|
42
|
+
allow_development_issuer?: boolean;
|
|
43
|
+
/** Accept self_reported host binding as ok for the requested profile. Default false for minted+. */
|
|
44
|
+
allow_self_reported?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Seal a draft package as minted.
|
|
48
|
+
* Content under signatures/ may change; package_digest (content) stays fixed after finalize.
|
|
49
|
+
*
|
|
50
|
+
* Trust rules:
|
|
51
|
+
* - Forbidden hosts (human/cli/shell/manual/…) refuse mint entirely.
|
|
52
|
+
* - Env-only SKILL_HOST without agent runtime markers still mints, but as self_reported
|
|
53
|
+
* + public_dev_hmac — never production trust.
|
|
54
|
+
* - Seal binds sealed_manifest_digest (identity + policy + content claims).
|
|
55
|
+
*/
|
|
56
|
+
export declare function mintSkillPackage(pkg: SkillPackageFiles, opts: MintOptions): {
|
|
57
|
+
files: SkillPackageFiles;
|
|
58
|
+
packageBytes: Uint8Array;
|
|
59
|
+
attestation: CreationAttestation;
|
|
60
|
+
};
|
|
61
|
+
export declare function addPermanenceAnchor(archive: Uint8Array, anchor: Omit<PermanenceAnchor, "package_digest"> & {
|
|
62
|
+
package_digest?: string;
|
|
63
|
+
}): Uint8Array;
|
|
64
|
+
export declare function verifyMintTrust(archive: Uint8Array, profile?: TrustProfile, issuer_secret_or_opts?: string | VerifyMintTrustOptions): {
|
|
65
|
+
ok: boolean;
|
|
66
|
+
issues: ValidationIssue[];
|
|
67
|
+
attestation?: CreationAttestation;
|
|
68
|
+
trust_state: TrustState;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* TrustView from skill.json + signatures + digests only — no compile, no model body ingest.
|
|
72
|
+
*/
|
|
73
|
+
export declare function inspectTrustView(archive: Uint8Array): TrustView;
|