@hasna/skills 0.9.12 → 0.9.13
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/bin/index.js +71 -18
- package/bin/maintenance.js +73 -0
- package/bin/mcp.js +63 -18
- package/bin/migrate.js +36 -1
- package/bin/server.js +96 -25
- package/bin/worker.js +74 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +64 -17
- package/dist/lib/agent-discovery.d.ts +1 -1
- package/dist/lib/claude-marketplace-registry.d.ts +6 -0
- package/dist/lib/claude-settings-witness.d.ts +7 -0
- package/dist/sdk/index.js +138 -22
- package/dist/server/rows.d.ts +2 -1
- package/dist/server/sqlite-store.d.ts +2 -1
- package/dist/server/store.d.ts +3 -1
- package/dist/server/types.d.ts +18 -0
- package/docs/plugin-admission.md +36 -0
- package/package.json +1 -1
package/bin/server.js
CHANGED
|
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
24122
24122
|
// package.json
|
|
24123
24123
|
var package_default = {
|
|
24124
24124
|
name: "@hasna/skills",
|
|
24125
|
-
version: "0.9.
|
|
24125
|
+
version: "0.9.13",
|
|
24126
24126
|
description: "Skills library for AI coding agents",
|
|
24127
24127
|
type: "module",
|
|
24128
24128
|
bin: {
|
|
@@ -35194,6 +35194,18 @@ function rowToSkill(row) {
|
|
|
35194
35194
|
...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
|
|
35195
35195
|
};
|
|
35196
35196
|
}
|
|
35197
|
+
function rowToPublishedSelectionState(row) {
|
|
35198
|
+
return {
|
|
35199
|
+
slug: String(row.slug),
|
|
35200
|
+
version: String(row.version),
|
|
35201
|
+
current: row.skill_exists ? {
|
|
35202
|
+
lifecycle: row.lifecycle === "archived" ? "archived" : "active",
|
|
35203
|
+
...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
|
|
35204
|
+
} : null,
|
|
35205
|
+
versionBundleSha256: typeof row.bundle_sha256 === "string" ? row.bundle_sha256 : null,
|
|
35206
|
+
bundleAvailable: Boolean(row.bundle_available)
|
|
35207
|
+
};
|
|
35208
|
+
}
|
|
35197
35209
|
function rowToPin(row) {
|
|
35198
35210
|
return {
|
|
35199
35211
|
orgId: String(row.org_id),
|
|
@@ -35266,6 +35278,25 @@ function rowToSkillVersion(row) {
|
|
|
35266
35278
|
};
|
|
35267
35279
|
}
|
|
35268
35280
|
|
|
35281
|
+
// src/lib/profile-limits.ts
|
|
35282
|
+
var MAX_PROFILE_SELECTIONS = 4096;
|
|
35283
|
+
var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
35284
|
+
var MAX_SKILL_SESSION_ID_CHARS = 256;
|
|
35285
|
+
var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
|
|
35286
|
+
var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
|
|
35287
|
+
function profileDocumentBytes(value) {
|
|
35288
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
35289
|
+
}
|
|
35290
|
+
function resolvedProfileSnapshot(profile2, authority) {
|
|
35291
|
+
return {
|
|
35292
|
+
profileId: profile2.id,
|
|
35293
|
+
workspaceId: profile2.workspaceId,
|
|
35294
|
+
profileRevision: profile2.revision,
|
|
35295
|
+
authority,
|
|
35296
|
+
selections: profile2.selections.map((selection) => ({ ...selection, authority, workspaceId: profile2.workspaceId, profileRevision: profile2.revision }))
|
|
35297
|
+
};
|
|
35298
|
+
}
|
|
35299
|
+
|
|
35269
35300
|
// src/server/types.ts
|
|
35270
35301
|
class StaleLeaseGenerationError extends Error {
|
|
35271
35302
|
runId;
|
|
@@ -35968,6 +35999,22 @@ class SqliteSkillsStore {
|
|
|
35968
35999
|
const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version2]);
|
|
35969
36000
|
return row ? rowToSkillVersion(row) : null;
|
|
35970
36001
|
}
|
|
36002
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
36003
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
36004
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
36005
|
+
if (!selections2.length)
|
|
36006
|
+
return [];
|
|
36007
|
+
const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
|
|
36008
|
+
return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
|
|
36009
|
+
r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
|
|
36010
|
+
v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
|
|
36011
|
+
FROM json_each(?) q
|
|
36012
|
+
LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
|
|
36013
|
+
LEFT JOIN skills_versions v ON v.org_id = ?
|
|
36014
|
+
AND v.slug = json_extract(q.value, '$.slug') AND v.version = json_extract(q.value, '$.version')
|
|
36015
|
+
LEFT JOIN skills_bundles b ON b.org_id = ? AND b.sha256 = v.bundle_sha256
|
|
36016
|
+
ORDER BY CAST(q.key AS INTEGER)`, [requested, principal.orgId, principal.orgId, principal.orgId]).map(rowToPublishedSelectionState);
|
|
36017
|
+
}
|
|
35971
36018
|
async pinSkill(principal, slug, metadata = {}) {
|
|
35972
36019
|
const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
|
|
35973
36020
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -36532,25 +36579,6 @@ function selectionAliasError(selections2) {
|
|
|
36532
36579
|
}
|
|
36533
36580
|
}
|
|
36534
36581
|
|
|
36535
|
-
// src/lib/profile-limits.ts
|
|
36536
|
-
var MAX_PROFILE_SELECTIONS = 4096;
|
|
36537
|
-
var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
36538
|
-
var MAX_SKILL_SESSION_ID_CHARS = 256;
|
|
36539
|
-
var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
|
|
36540
|
-
var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
|
|
36541
|
-
function profileDocumentBytes(value) {
|
|
36542
|
-
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
36543
|
-
}
|
|
36544
|
-
function resolvedProfileSnapshot(profile2, authority) {
|
|
36545
|
-
return {
|
|
36546
|
-
profileId: profile2.id,
|
|
36547
|
-
workspaceId: profile2.workspaceId,
|
|
36548
|
-
profileRevision: profile2.revision,
|
|
36549
|
-
authority,
|
|
36550
|
-
selections: profile2.selections.map((selection) => ({ ...selection, authority, workspaceId: profile2.workspaceId, profileRevision: profile2.revision }))
|
|
36551
|
-
};
|
|
36552
|
-
}
|
|
36553
|
-
|
|
36554
36582
|
// src/lib/selection-cache.ts
|
|
36555
36583
|
class SkillSelectionError extends Error {
|
|
36556
36584
|
code;
|
|
@@ -37601,16 +37629,21 @@ function selected(value) {
|
|
|
37601
37629
|
return selections2;
|
|
37602
37630
|
}
|
|
37603
37631
|
async function validatePublished(store, principal, selections2) {
|
|
37604
|
-
|
|
37605
|
-
|
|
37632
|
+
const states = await store.getPublishedSelectionStates(principal, selections2);
|
|
37633
|
+
if (states.length !== selections2.length)
|
|
37634
|
+
throw new SkillRequestError(503, "SELECTION_UNAVAILABLE", "Selected publication state is unavailable");
|
|
37635
|
+
for (const [index, selection2] of selections2.entries()) {
|
|
37636
|
+
const state2 = states[index];
|
|
37637
|
+
if (!state2 || state2.slug !== selection2.slug || state2.version !== selection2.version)
|
|
37638
|
+
throw new SkillRequestError(503, "SELECTION_UNAVAILABLE", "Selected publication state is unavailable");
|
|
37639
|
+
const current = state2.current;
|
|
37606
37640
|
if (!current || current.tombstonedAt)
|
|
37607
37641
|
throw new SkillRequestError(current?.tombstonedAt ? 410 : 404, "SELECTION_UNAVAILABLE", `Selected skill '${selection2.slug}' is unavailable`);
|
|
37608
37642
|
if (current.lifecycle === "archived")
|
|
37609
37643
|
throw new SkillRequestError(409, "SELECTION_ARCHIVED", `Selected skill '${selection2.slug}' is archived`);
|
|
37610
|
-
|
|
37611
|
-
if (!version2 || `sha256:${version2.bundleSha256}` !== selection2.bundleDigest)
|
|
37644
|
+
if (!state2.versionBundleSha256 || `sha256:${state2.versionBundleSha256}` !== selection2.bundleDigest)
|
|
37612
37645
|
throw new SkillRequestError(409, "SELECTION_VERSION_MISMATCH", `Selected version of '${selection2.slug}' does not match its published digest`);
|
|
37613
|
-
if (!
|
|
37646
|
+
if (!state2.bundleAvailable)
|
|
37614
37647
|
throw new SkillRequestError(503, "SELECTION_BUNDLE_UNAVAILABLE", "Selected bundle is unavailable");
|
|
37615
37648
|
}
|
|
37616
37649
|
}
|
|
@@ -43171,6 +43204,25 @@ class MemorySkillsStore {
|
|
|
43171
43204
|
const found = this.versions.get(versionKey(principal.orgId, slug, version2));
|
|
43172
43205
|
return found && found.orgId === principal.orgId ? found : null;
|
|
43173
43206
|
}
|
|
43207
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
43208
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
43209
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
43210
|
+
return selections2.map(({ slug, version: version2 }) => {
|
|
43211
|
+
const current = this.skills.get(skillKey(principal.orgId, slug));
|
|
43212
|
+
const selectedVersion = this.versions.get(versionKey(principal.orgId, slug, version2));
|
|
43213
|
+
const bundle = selectedVersion && this.bundles.get(skillKey(principal.orgId, selectedVersion.bundleSha256));
|
|
43214
|
+
return {
|
|
43215
|
+
slug,
|
|
43216
|
+
version: version2,
|
|
43217
|
+
current: current?.orgId === principal.orgId ? {
|
|
43218
|
+
lifecycle: current.lifecycle,
|
|
43219
|
+
...current.tombstonedAt ? { tombstonedAt: current.tombstonedAt } : {}
|
|
43220
|
+
} : null,
|
|
43221
|
+
versionBundleSha256: selectedVersion?.orgId === principal.orgId ? selectedVersion.bundleSha256 : null,
|
|
43222
|
+
bundleAvailable: bundle?.orgId === principal.orgId
|
|
43223
|
+
};
|
|
43224
|
+
});
|
|
43225
|
+
}
|
|
43174
43226
|
async listSkills(principal) {
|
|
43175
43227
|
return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt).sort((a4, b4) => a4.slug.localeCompare(b4.slug));
|
|
43176
43228
|
}
|
|
@@ -43979,6 +44031,25 @@ class PostgresSkillsStore {
|
|
|
43979
44031
|
`;
|
|
43980
44032
|
return rows[0] ? rowToSkillVersion(rows[0]) : null;
|
|
43981
44033
|
}
|
|
44034
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
44035
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
44036
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
44037
|
+
if (!selections2.length)
|
|
44038
|
+
return [];
|
|
44039
|
+
const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
|
|
44040
|
+
const rows = await this.sql`
|
|
44041
|
+
SELECT q.value->>'slug' AS slug, q.value->>'version' AS version,
|
|
44042
|
+
r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
|
|
44043
|
+
v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
|
|
44044
|
+
FROM jsonb_array_elements(${requested}::text::jsonb) WITH ORDINALITY AS q(value, ordinal)
|
|
44045
|
+
LEFT JOIN skills_registry r ON r.org_id = ${principal.orgId} AND r.slug = q.value->>'slug'
|
|
44046
|
+
LEFT JOIN skills_versions v ON v.org_id = ${principal.orgId}
|
|
44047
|
+
AND v.slug = q.value->>'slug' AND v.version = q.value->>'version'
|
|
44048
|
+
LEFT JOIN skills_bundles b ON b.org_id = ${principal.orgId} AND b.sha256 = v.bundle_sha256
|
|
44049
|
+
ORDER BY q.ordinal
|
|
44050
|
+
`;
|
|
44051
|
+
return rows.map(rowToPublishedSelectionState);
|
|
44052
|
+
}
|
|
43982
44053
|
async pinSkill(principal, slug, metadata = {}) {
|
|
43983
44054
|
const rows = await this.sql`
|
|
43984
44055
|
INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
|
package/bin/worker.js
CHANGED
|
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
24125
24125
|
// package.json
|
|
24126
24126
|
var package_default = {
|
|
24127
24127
|
name: "@hasna/skills",
|
|
24128
|
-
version: "0.9.
|
|
24128
|
+
version: "0.9.13",
|
|
24129
24129
|
description: "Skills library for AI coding agents",
|
|
24130
24130
|
type: "module",
|
|
24131
24131
|
bin: {
|
|
@@ -34942,6 +34942,18 @@ function rowToSkill(row) {
|
|
|
34942
34942
|
...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
|
|
34943
34943
|
};
|
|
34944
34944
|
}
|
|
34945
|
+
function rowToPublishedSelectionState(row) {
|
|
34946
|
+
return {
|
|
34947
|
+
slug: String(row.slug),
|
|
34948
|
+
version: String(row.version),
|
|
34949
|
+
current: row.skill_exists ? {
|
|
34950
|
+
lifecycle: row.lifecycle === "archived" ? "archived" : "active",
|
|
34951
|
+
...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
|
|
34952
|
+
} : null,
|
|
34953
|
+
versionBundleSha256: typeof row.bundle_sha256 === "string" ? row.bundle_sha256 : null,
|
|
34954
|
+
bundleAvailable: Boolean(row.bundle_available)
|
|
34955
|
+
};
|
|
34956
|
+
}
|
|
34945
34957
|
function rowToPin(row) {
|
|
34946
34958
|
return {
|
|
34947
34959
|
orgId: String(row.org_id),
|
|
@@ -35014,6 +35026,13 @@ function rowToSkillVersion(row) {
|
|
|
35014
35026
|
};
|
|
35015
35027
|
}
|
|
35016
35028
|
|
|
35029
|
+
// src/lib/profile-limits.ts
|
|
35030
|
+
var MAX_PROFILE_SELECTIONS = 4096;
|
|
35031
|
+
var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
35032
|
+
var MAX_SKILL_SESSION_ID_CHARS = 256;
|
|
35033
|
+
var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
|
|
35034
|
+
var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
|
|
35035
|
+
|
|
35017
35036
|
// src/server/types.ts
|
|
35018
35037
|
class StaleLeaseGenerationError extends Error {
|
|
35019
35038
|
runId;
|
|
@@ -35715,6 +35734,22 @@ class SqliteSkillsStore {
|
|
|
35715
35734
|
const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version2]);
|
|
35716
35735
|
return row ? rowToSkillVersion(row) : null;
|
|
35717
35736
|
}
|
|
35737
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
35738
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
35739
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
35740
|
+
if (!selections2.length)
|
|
35741
|
+
return [];
|
|
35742
|
+
const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
|
|
35743
|
+
return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
|
|
35744
|
+
r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
|
|
35745
|
+
v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
|
|
35746
|
+
FROM json_each(?) q
|
|
35747
|
+
LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
|
|
35748
|
+
LEFT JOIN skills_versions v ON v.org_id = ?
|
|
35749
|
+
AND v.slug = json_extract(q.value, '$.slug') AND v.version = json_extract(q.value, '$.version')
|
|
35750
|
+
LEFT JOIN skills_bundles b ON b.org_id = ? AND b.sha256 = v.bundle_sha256
|
|
35751
|
+
ORDER BY CAST(q.key AS INTEGER)`, [requested, principal.orgId, principal.orgId, principal.orgId]).map(rowToPublishedSelectionState);
|
|
35752
|
+
}
|
|
35718
35753
|
async pinSkill(principal, slug, metadata = {}) {
|
|
35719
35754
|
const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
|
|
35720
35755
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -35996,13 +36031,6 @@ var init_client = __esm2(() => {
|
|
|
35996
36031
|
init_client();
|
|
35997
36032
|
init_client();
|
|
35998
36033
|
|
|
35999
|
-
// src/lib/profile-limits.ts
|
|
36000
|
-
var MAX_PROFILE_SELECTIONS = 4096;
|
|
36001
|
-
var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
36002
|
-
var MAX_SKILL_SESSION_ID_CHARS = 256;
|
|
36003
|
-
var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
|
|
36004
|
-
var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
|
|
36005
|
-
|
|
36006
36034
|
// src/lib/selection-cache.ts
|
|
36007
36035
|
var MAX_CACHED_PROFILE_AGE_MS = 24 * 60 * 60 * 1000;
|
|
36008
36036
|
|
|
@@ -36707,6 +36735,25 @@ class MemorySkillsStore {
|
|
|
36707
36735
|
const found = this.versions.get(versionKey(principal.orgId, slug, version2));
|
|
36708
36736
|
return found && found.orgId === principal.orgId ? found : null;
|
|
36709
36737
|
}
|
|
36738
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
36739
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
36740
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
36741
|
+
return selections2.map(({ slug, version: version2 }) => {
|
|
36742
|
+
const current = this.skills.get(skillKey(principal.orgId, slug));
|
|
36743
|
+
const selectedVersion = this.versions.get(versionKey(principal.orgId, slug, version2));
|
|
36744
|
+
const bundle = selectedVersion && this.bundles.get(skillKey(principal.orgId, selectedVersion.bundleSha256));
|
|
36745
|
+
return {
|
|
36746
|
+
slug,
|
|
36747
|
+
version: version2,
|
|
36748
|
+
current: current?.orgId === principal.orgId ? {
|
|
36749
|
+
lifecycle: current.lifecycle,
|
|
36750
|
+
...current.tombstonedAt ? { tombstonedAt: current.tombstonedAt } : {}
|
|
36751
|
+
} : null,
|
|
36752
|
+
versionBundleSha256: selectedVersion?.orgId === principal.orgId ? selectedVersion.bundleSha256 : null,
|
|
36753
|
+
bundleAvailable: bundle?.orgId === principal.orgId
|
|
36754
|
+
};
|
|
36755
|
+
});
|
|
36756
|
+
}
|
|
36710
36757
|
async listSkills(principal) {
|
|
36711
36758
|
return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt).sort((a3, b3) => a3.slug.localeCompare(b3.slug));
|
|
36712
36759
|
}
|
|
@@ -37515,6 +37562,25 @@ class PostgresSkillsStore {
|
|
|
37515
37562
|
`;
|
|
37516
37563
|
return rows[0] ? rowToSkillVersion(rows[0]) : null;
|
|
37517
37564
|
}
|
|
37565
|
+
async getPublishedSelectionStates(principal, selections2) {
|
|
37566
|
+
if (selections2.length > MAX_PROFILE_SELECTIONS)
|
|
37567
|
+
throw new RangeError("Published selection lookup exceeds the profile selection limit");
|
|
37568
|
+
if (!selections2.length)
|
|
37569
|
+
return [];
|
|
37570
|
+
const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
|
|
37571
|
+
const rows = await this.sql`
|
|
37572
|
+
SELECT q.value->>'slug' AS slug, q.value->>'version' AS version,
|
|
37573
|
+
r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
|
|
37574
|
+
v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
|
|
37575
|
+
FROM jsonb_array_elements(${requested}::text::jsonb) WITH ORDINALITY AS q(value, ordinal)
|
|
37576
|
+
LEFT JOIN skills_registry r ON r.org_id = ${principal.orgId} AND r.slug = q.value->>'slug'
|
|
37577
|
+
LEFT JOIN skills_versions v ON v.org_id = ${principal.orgId}
|
|
37578
|
+
AND v.slug = q.value->>'slug' AND v.version = q.value->>'version'
|
|
37579
|
+
LEFT JOIN skills_bundles b ON b.org_id = ${principal.orgId} AND b.sha256 = v.bundle_sha256
|
|
37580
|
+
ORDER BY q.ordinal
|
|
37581
|
+
`;
|
|
37582
|
+
return rows.map(rowToPublishedSelectionState);
|
|
37583
|
+
}
|
|
37518
37584
|
async pinSkill(principal, slug, metadata = {}) {
|
|
37519
37585
|
const rows = await this.sql`
|
|
37520
37586
|
INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
|
package/dist/index.d.ts
CHANGED
|
@@ -62,8 +62,8 @@ export { syncSelectionProfile } from "./lib/selection-resolver.js";
|
|
|
62
62
|
export { inspectSkillSession, reconcileSkillSession, type SessionReconciliationInput, type SessionReconciliationOptions } from "./lib/session-reconciliation.js";
|
|
63
63
|
export { planAgentIntegration, planClaudeStopHookUpdate, applyAgentIntegration, inventoryNativeSkills, archiveNativeSkills } from "./lib/agent-integration.js";
|
|
64
64
|
export { captureDiscoveryDirectories, captureDiscoveryByteSources, captureDiscoveryPathSources, type DiscoveryDirectory, type DiscoverySource } from "./lib/agent-discovery.js";
|
|
65
|
-
export { captureClaudeMarketplaceRegistry } from "./lib/claude-marketplace-registry.js";
|
|
66
|
-
export { captureClaudeSettings } from "./lib/claude-settings-witness.js";
|
|
65
|
+
export { captureClaudeMarketplaceRegistry, captureClaudeMarketplaceRegistryV2 } from "./lib/claude-marketplace-registry.js";
|
|
66
|
+
export { captureClaudeSettings, captureClaudeSettingsV2 } from "./lib/claude-settings-witness.js";
|
|
67
67
|
export { buildPluginProjection, pluginTreeDigest, type PluginProjectionManifest, type PluginPayloadMapping, type PluginFileWitness } from "./lib/plugin-projection.js";
|
|
68
68
|
export { planPluginAdmission, admitPlugin, resolveAdmittedPlugin, pluginResolverCommand, type PluginAdmissionTarget, type PluginAdmissionBinding, type PluginAdmissionPrincipal, type PluginAdmissionPlan, type PluginAdmissionIdentity, type PluginBundleIdentity, type PluginRoutingIdentity, type PluginAdmissionReceipt } from "./lib/plugin-admission.js";
|
|
69
69
|
export { captureManagedPluginRegistry, type ManagedPluginRegistrationWitness } from "./lib/plugin-discovery.js";
|
package/dist/index.js
CHANGED
|
@@ -9055,10 +9055,10 @@ function assertAgentPolicyCollections(policy) {
|
|
|
9055
9055
|
text(source.path);
|
|
9056
9056
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
9057
9057
|
if (source.hashMode !== undefined)
|
|
9058
|
-
requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
9059
|
-
if (source.hashMode === "claude-settings-v1")
|
|
9058
|
+
requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
9059
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
|
|
9060
9060
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve2(source.path) === source.path && basename2(source.path) === "settings.json");
|
|
9061
|
-
if (source.hashMode === "claude-marketplace-registry")
|
|
9061
|
+
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
|
|
9062
9062
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve2(source.path) === source.path && basename2(source.path) === "known_marketplaces.json");
|
|
9063
9063
|
if (source.hashMode === "claude-plugin-registry") {
|
|
9064
9064
|
requireBound(agent === "claude");
|
|
@@ -9610,7 +9610,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
|
|
|
9610
9610
|
// package.json
|
|
9611
9611
|
var package_default = {
|
|
9612
9612
|
name: "@hasna/skills",
|
|
9613
|
-
version: "0.9.
|
|
9613
|
+
version: "0.9.13",
|
|
9614
9614
|
description: "Skills library for AI coding agents",
|
|
9615
9615
|
type: "module",
|
|
9616
9616
|
bin: {
|
|
@@ -12867,9 +12867,33 @@ function assertClaudeStopHookReplacement(before, replacement) {
|
|
|
12867
12867
|
}
|
|
12868
12868
|
need2(JSON.stringify(withoutStop(original)) === JSON.stringify(withoutStop(next)), "Stop update changed settings outside hooks.Stop");
|
|
12869
12869
|
}
|
|
12870
|
-
|
|
12870
|
+
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
12871
|
+
function inferencePreference(key, child) {
|
|
12872
|
+
if (key === "skipDangerousModePermissionPrompt") {
|
|
12873
|
+
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
12874
|
+
return true;
|
|
12875
|
+
}
|
|
12876
|
+
if (key === "effortLevel") {
|
|
12877
|
+
need2(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
|
|
12878
|
+
return true;
|
|
12879
|
+
}
|
|
12880
|
+
if (key !== "modelSettings")
|
|
12881
|
+
return false;
|
|
12882
|
+
need2(child.kind === "object", "requires an object of model preferences");
|
|
12883
|
+
child.entries = child.entries.filter(([model, preferences]) => {
|
|
12884
|
+
if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
|
|
12885
|
+
return true;
|
|
12886
|
+
const effort = preferences.entries[0][1];
|
|
12887
|
+
need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
12888
|
+
return false;
|
|
12889
|
+
});
|
|
12890
|
+
return child.entries.length === 0;
|
|
12891
|
+
}
|
|
12892
|
+
function settingsDigest(text2, version = 1) {
|
|
12871
12893
|
const value = parse3(text2);
|
|
12872
12894
|
value.entries = value.entries.filter(([key, child]) => {
|
|
12895
|
+
if (version === 2 && inferencePreference(key, child))
|
|
12896
|
+
return false;
|
|
12873
12897
|
if (key === "model") {
|
|
12874
12898
|
need2(child.kind === "string", "contains an invalid model selection");
|
|
12875
12899
|
return !BUILTIN_MODEL_SELECTIONS.has(child.value);
|
|
@@ -12884,7 +12908,7 @@ function settingsDigest(text2) {
|
|
|
12884
12908
|
}
|
|
12885
12909
|
return true;
|
|
12886
12910
|
});
|
|
12887
|
-
return createHash9("sha256").update(
|
|
12911
|
+
return createHash9("sha256").update(`hasna.skills.claude-settings.v${version}\x00`).update(JSON.stringify(canonical2(value))).digest("hex");
|
|
12888
12912
|
}
|
|
12889
12913
|
function hashClaudeSettingsReplacement(text2, budget) {
|
|
12890
12914
|
need2(typeof text2 === "string", "requires settings text");
|
|
@@ -12896,6 +12920,16 @@ function hashClaudeSettingsReplacement(text2, budget) {
|
|
|
12896
12920
|
function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12897
12921
|
return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readSettings(path, budget)) };
|
|
12898
12922
|
}
|
|
12923
|
+
function captureClaudeSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12924
|
+
return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readSettings(path, budget), 2) };
|
|
12925
|
+
}
|
|
12926
|
+
function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
12927
|
+
need2(typeof text2 === "string", "requires settings text");
|
|
12928
|
+
const bytes = Buffer.byteLength(text2);
|
|
12929
|
+
need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
12930
|
+
budget.remaining -= bytes;
|
|
12931
|
+
return settingsDigest(text2, 2);
|
|
12932
|
+
}
|
|
12899
12933
|
|
|
12900
12934
|
// src/lib/agent-integration.ts
|
|
12901
12935
|
var sha = (data) => createHash10("sha256").update(data).digest("hex");
|
|
@@ -16261,9 +16295,13 @@ function textField(value) {
|
|
|
16261
16295
|
need3(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
16262
16296
|
return value.value;
|
|
16263
16297
|
}
|
|
16264
|
-
function normalizeRow(row) {
|
|
16265
|
-
if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
|
|
16266
|
-
|
|
16298
|
+
function normalizeRow(row, version = 1) {
|
|
16299
|
+
if (!exactKeys(row, ["source", "installLocation", "lastUpdated"])) {
|
|
16300
|
+
if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
|
|
16301
|
+
return;
|
|
16302
|
+
const autoUpdate = field(row, "autoUpdate");
|
|
16303
|
+
need3(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
|
|
16304
|
+
}
|
|
16267
16305
|
const source = field(row, "source");
|
|
16268
16306
|
if (source?.kind !== "object")
|
|
16269
16307
|
return;
|
|
@@ -16290,6 +16328,13 @@ function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024
|
|
|
16290
16328
|
const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
16291
16329
|
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
16292
16330
|
}
|
|
16331
|
+
function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
16332
|
+
const value = parse6(readRegistry2(path, budget));
|
|
16333
|
+
for (const [, row] of value.entries)
|
|
16334
|
+
normalizeRow(row, 2);
|
|
16335
|
+
const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
|
|
16336
|
+
return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
|
|
16337
|
+
}
|
|
16293
16338
|
|
|
16294
16339
|
// src/lib/codex-hook-discovery-recovery.ts
|
|
16295
16340
|
import { createHash as createHash15 } from "crypto";
|
|
@@ -16393,18 +16438,18 @@ function read2(path, changes) {
|
|
|
16393
16438
|
return readFileSync16(path, "utf8");
|
|
16394
16439
|
}
|
|
16395
16440
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
16396
|
-
if (source.hashMode === "claude-settings-v1") {
|
|
16441
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
16397
16442
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
16398
16443
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
16399
|
-
const current = captureClaudeSettings(source.path, budget).sha256;
|
|
16444
|
+
const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
16400
16445
|
if (current !== source.sha256)
|
|
16401
16446
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
16402
|
-
return changes?.has(source.path) ? hashClaudeSettingsReplacement(changes.get(source.path), budget) : current;
|
|
16447
|
+
return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
|
|
16403
16448
|
}
|
|
16404
|
-
if (source.hashMode === "claude-marketplace-registry") {
|
|
16449
|
+
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
16405
16450
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256) || changes?.has(source.path))
|
|
16406
16451
|
throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
|
|
16407
|
-
const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
|
|
16452
|
+
const sha2562 = (source.hashMode === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : captureClaudeMarketplaceRegistry)(source.path, budget).sha256;
|
|
16408
16453
|
if (sha2562 !== source.sha256)
|
|
16409
16454
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
16410
16455
|
return sha2562;
|
|
@@ -16461,9 +16506,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
16461
16506
|
safe4(root2);
|
|
16462
16507
|
}
|
|
16463
16508
|
function assertMarketplaceBinding(binding, source) {
|
|
16464
|
-
if (source.hashMode === "claude-settings-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
16509
|
+
if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
16465
16510
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
16466
|
-
if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
16511
|
+
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
16467
16512
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
16468
16513
|
}
|
|
16469
16514
|
function rebindAgentDiscovery(binding, changes) {
|
|
@@ -16604,7 +16649,7 @@ function resolveAgentDiscovery(options) {
|
|
|
16604
16649
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
16605
16650
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
16606
16651
|
verifyAgentDiscovery(supplied);
|
|
16607
|
-
if (review.sources.some((source) => source.hashMode === "claude-settings-v1" && source.path !== canonical4(configPath)))
|
|
16652
|
+
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
16608
16653
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
16609
16654
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
16610
16655
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -43728,7 +43773,9 @@ export {
|
|
|
43728
43773
|
captureDiscoveryPathSources,
|
|
43729
43774
|
captureDiscoveryDirectories,
|
|
43730
43775
|
captureDiscoveryByteSources,
|
|
43776
|
+
captureClaudeSettingsV2,
|
|
43731
43777
|
captureClaudeSettings,
|
|
43778
|
+
captureClaudeMarketplaceRegistryV2,
|
|
43732
43779
|
captureClaudeMarketplaceRegistry,
|
|
43733
43780
|
canonicalizeManifest,
|
|
43734
43781
|
buildSkillsS3ObjectUrl,
|
|
@@ -6,7 +6,7 @@ export { captureDiscoveryDirectories, type DiscoveryDirectory } from "./agent-di
|
|
|
6
6
|
export interface DiscoverySource {
|
|
7
7
|
path: string;
|
|
8
8
|
sha256: string | null;
|
|
9
|
-
hashMode?: "bytes" | "path-bytes" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1";
|
|
9
|
+
hashMode?: "bytes" | "path-bytes" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1" | "claude-settings-v2" | "claude-marketplace-registry-v2";
|
|
10
10
|
managedPlugins?: ManagedPluginRegistrationWitness[];
|
|
11
11
|
format?: "json" | "toml" | "yaml";
|
|
12
12
|
fields?: string[];
|
|
@@ -15,3 +15,9 @@ export declare function captureClaudeMarketplaceRegistry(path: string, budget?:
|
|
|
15
15
|
hashMode: "claude-marketplace-registry";
|
|
16
16
|
sha256: string;
|
|
17
17
|
};
|
|
18
|
+
/** Explicit v2 review also recognizes rows with a bound boolean autoUpdate. */
|
|
19
|
+
export declare function captureClaudeMarketplaceRegistryV2(path: string, budget?: ClaudeMarketplaceRegistryBudget): {
|
|
20
|
+
path: string;
|
|
21
|
+
hashMode: "claude-marketplace-registry-v2";
|
|
22
|
+
sha256: string;
|
|
23
|
+
};
|
|
@@ -23,3 +23,10 @@ export declare function captureClaudeSettings(path: string, budget?: ClaudeSetti
|
|
|
23
23
|
hashMode: "claude-settings-v1";
|
|
24
24
|
sha256: string;
|
|
25
25
|
};
|
|
26
|
+
/** A new explicit review is required; v1 witnesses retain their old meaning. */
|
|
27
|
+
export declare function captureClaudeSettingsV2(path: string, budget?: ClaudeSettingsWitnessBudget): {
|
|
28
|
+
path: string;
|
|
29
|
+
hashMode: "claude-settings-v2";
|
|
30
|
+
sha256: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function hashClaudeSettingsReplacementV2(text: string, budget: ClaudeSettingsWitnessBudget): string;
|