@hasna/skills 0.8.12 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/bin/index.js +183 -13
- package/bin/mcp.js +248 -154
- package/bin/migrate.js +100 -10
- package/bin/server.js +596 -51
- package/bin/worker.js +283 -20
- package/dist/cli/commands/registry.d.ts +1 -0
- package/dist/index.js +88 -9
- package/dist/lib/cloud-executions.d.ts +20 -0
- package/dist/lib/registry-types.d.ts +3 -0
- package/dist/lib/remote-client.d.ts +11 -0
- package/dist/lib/revision.d.ts +3 -0
- package/dist/sdk/execution/admission.d.ts +2 -1
- package/dist/sdk/execution/types.d.ts +9 -0
- package/dist/sdk/index.d.ts +1 -0
- package/dist/sdk/index.js +1267 -498
- package/dist/server/runtime-policy.d.ts +4 -2
- package/dist/server/runtime-pure-contract.d.ts +51 -0
- package/dist/server/runtime-store.d.ts +4 -0
- package/dist/server/runtime-worker.d.ts +2 -1
- package/dist/server/selection-store.d.ts +9 -1
- package/dist/server/skills-api.d.ts +2 -1
- package/dist/server/sqlite-store.d.ts +4 -1
- package/dist/server/store.d.ts +6 -1
- package/dist/server/types.d.ts +27 -0
- package/migrations/postgres/0010_skill_archive_lifecycle.sql +5 -0
- package/migrations/sqlite/0010_skill_archive_lifecycle.sql +5 -0
- package/package.json +1 -1
package/bin/mcp.js
CHANGED
|
@@ -20019,7 +20019,7 @@ var package_default;
|
|
|
20019
20019
|
var init_package = __esm(() => {
|
|
20020
20020
|
package_default = {
|
|
20021
20021
|
name: "@hasna/skills",
|
|
20022
|
-
version: "0.
|
|
20022
|
+
version: "0.9.1",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
|
@@ -37439,6 +37439,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
|
|
|
37439
37439
|
const version2 = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
|
|
37440
37440
|
const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
|
|
37441
37441
|
const commands = parseManifestCommands(jsonManifest) ?? (kind === "instruction" ? [] : inferPackageCommands(pkg, name)) ?? [];
|
|
37442
|
+
const runtime = parseManifestRuntime(jsonManifest) ?? inferRuntimeFromCommands(commands);
|
|
37442
37443
|
return {
|
|
37443
37444
|
$schema: stringField(jsonManifest, "$schema") ?? PORTABLE_SKILL_SCHEMA,
|
|
37444
37445
|
standard: stringField(jsonManifest, "standard") ?? PORTABLE_SKILL_STANDARD,
|
|
@@ -37451,7 +37452,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
|
|
|
37451
37452
|
...kind ? { kind } : {},
|
|
37452
37453
|
inputs: kind === "instruction" ? parseManifestInputs(jsonManifest) ?? [] : parseManifestInputs(jsonManifest) ?? DEFAULT_INPUTS,
|
|
37453
37454
|
commands,
|
|
37454
|
-
...
|
|
37455
|
+
...runtime ? { runtime } : {},
|
|
37455
37456
|
...parseManifestProvenance(jsonManifest) ? { provenance: parseManifestProvenance(jsonManifest) } : {}
|
|
37456
37457
|
};
|
|
37457
37458
|
}
|
|
@@ -37970,6 +37971,12 @@ function inferPackageCommands(pkg, fallbackName) {
|
|
|
37970
37971
|
}
|
|
37971
37972
|
return;
|
|
37972
37973
|
}
|
|
37974
|
+
function inferRuntimeFromCommands(commands) {
|
|
37975
|
+
const entrypoint = commands.find((command) => command.entry)?.entry;
|
|
37976
|
+
if (!entrypoint || !entrypoint.toLowerCase().endsWith(".py"))
|
|
37977
|
+
return;
|
|
37978
|
+
return { ...defaultRuntimeContract(entrypoint), runtime: "python3" };
|
|
37979
|
+
}
|
|
37973
37980
|
function readJsonObject(path) {
|
|
37974
37981
|
const parsed = JSON.parse(readFileSync8(path, "utf-8"));
|
|
37975
37982
|
if (!isRecord2(parsed))
|
|
@@ -38786,6 +38793,8 @@ function normalizeRemoteSkill(skill) {
|
|
|
38786
38793
|
tags: skill.tags || ["remote"],
|
|
38787
38794
|
dependencies: skill.dependencies,
|
|
38788
38795
|
...skill.version ? { version: skill.version } : {},
|
|
38796
|
+
...skill.revisionId !== undefined ? { revisionId: skill.revisionId } : {},
|
|
38797
|
+
...skill.lifecycle ? { lifecycle: skill.lifecycle } : {},
|
|
38789
38798
|
availability: normalizeRemoteAvailability(skill.availability),
|
|
38790
38799
|
source: "remote"
|
|
38791
38800
|
};
|
|
@@ -38889,6 +38898,8 @@ var init_remote_registry = __esm(() => {
|
|
|
38889
38898
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
38890
38899
|
dependencies: exports_external.array(exports_external.string()).optional(),
|
|
38891
38900
|
version: exports_external.string().optional(),
|
|
38901
|
+
revisionId: exports_external.union([exports_external.string().regex(/^[\x21-\x7e]+$/), exports_external.null()]).optional(),
|
|
38902
|
+
lifecycle: exports_external.enum(["active", "archived"]).optional(),
|
|
38892
38903
|
availability: remoteAvailabilitySchema.optional()
|
|
38893
38904
|
}).passthrough().refine((skill) => skill.name || skill.slug, {
|
|
38894
38905
|
message: "Remote skill requires name or slug"
|
|
@@ -49527,6 +49538,7 @@ __export(exports_remote_client, {
|
|
|
49527
49538
|
RemoteWorkspaceSelectionError: () => RemoteWorkspaceSelectionError,
|
|
49528
49539
|
RemoteWorkspaceMemberError: () => RemoteWorkspaceMemberError,
|
|
49529
49540
|
RemoteSkillsClient: () => RemoteSkillsClient,
|
|
49541
|
+
RemoteSkillLifecycleError: () => RemoteSkillLifecycleError,
|
|
49530
49542
|
RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
|
|
49531
49543
|
RemoteRequestError: () => RemoteRequestError,
|
|
49532
49544
|
RemoteQuoteUnavailableError: () => RemoteQuoteUnavailableError,
|
|
@@ -49658,6 +49670,39 @@ class RemoteSkillsClient {
|
|
|
49658
49670
|
})();
|
|
49659
49671
|
return this.capabilities;
|
|
49660
49672
|
}
|
|
49673
|
+
async addSkillPublishScope(keyId, expectedScopes, expectedOrgId) {
|
|
49674
|
+
if (!/^[A-Za-z0-9_-]+$/.test(keyId))
|
|
49675
|
+
throw new Error("Invalid API key id");
|
|
49676
|
+
if (!/^[A-Za-z0-9_-]{1,256}$/.test(expectedOrgId))
|
|
49677
|
+
throw new Error("Invalid expected organization id");
|
|
49678
|
+
if (!Array.isArray(expectedScopes) || expectedScopes.length > 32 || expectedScopes.some((scope) => typeof scope !== "string" || scope.length > 128 || !/^(?:\*|[a-z][a-z0-9_-]*:(?:\*|[a-z][a-z0-9_-]*))$/.test(scope)) || new Set(expectedScopes).size !== expectedScopes.length)
|
|
49679
|
+
throw new Error("Invalid expected API key scopes");
|
|
49680
|
+
const path = `/api/v1/admin/keys/${encodeURIComponent(keyId)}/scopes`;
|
|
49681
|
+
const response = await this.request(path, { method: "PATCH", body: JSON.stringify({ expected_scopes: expectedScopes, add_scopes: ["skills:publish"] }) });
|
|
49682
|
+
if (!response.ok)
|
|
49683
|
+
throw new RemoteRequestError(path, response.status);
|
|
49684
|
+
let value;
|
|
49685
|
+
try {
|
|
49686
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 64 * 1024)));
|
|
49687
|
+
} catch {
|
|
49688
|
+
throw new Error("Invalid API key scope update response");
|
|
49689
|
+
}
|
|
49690
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
49691
|
+
throw new Error("Invalid API key scope update response");
|
|
49692
|
+
const body = value;
|
|
49693
|
+
const returnedId = body.keyId;
|
|
49694
|
+
const orgId = body.orgId;
|
|
49695
|
+
const scopes = body.scopes;
|
|
49696
|
+
if (returnedId !== keyId || typeof orgId !== "string" || !orgId || expectedOrgId !== undefined && orgId !== expectedOrgId || !Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string") || body.updated !== true) {
|
|
49697
|
+
throw new Error("Invalid API key scope update response");
|
|
49698
|
+
}
|
|
49699
|
+
const returnedScopes = scopes;
|
|
49700
|
+
const expectedResult = expectedScopes.includes("skills:publish") ? expectedScopes : [...expectedScopes, "skills:publish"];
|
|
49701
|
+
if (returnedScopes.length !== expectedResult.length || expectedResult.some((scope, index) => returnedScopes[index] !== scope)) {
|
|
49702
|
+
throw new Error("API key scope update response did not preserve the expected scopes");
|
|
49703
|
+
}
|
|
49704
|
+
return { keyId, orgId, scopes: [...returnedScopes], updated: true };
|
|
49705
|
+
}
|
|
49661
49706
|
async submitQuotedRun(slug2, input2 = {}, args = [], approval = {}) {
|
|
49662
49707
|
runQuoteReceipt(approval.quoteReceipt);
|
|
49663
49708
|
({ input: input2, args, approval } = JSON.parse(JSON.stringify({ input: input2, args, approval })));
|
|
@@ -50175,6 +50220,19 @@ class RemoteSkillsClient {
|
|
|
50175
50220
|
async deleteSkill(slug2) {
|
|
50176
50221
|
return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}`, { method: "DELETE" });
|
|
50177
50222
|
}
|
|
50223
|
+
async setSkillLifecycle(slug2, lifecycle, options) {
|
|
50224
|
+
const path = `/api/v1/skills/${encodeURIComponent(slug2)}/lifecycle`;
|
|
50225
|
+
const response = await this.request(path, {
|
|
50226
|
+
method: "PATCH",
|
|
50227
|
+
headers: { "If-Match": options.expectedRevisionId },
|
|
50228
|
+
body: JSON.stringify({ lifecycle, ...options.reason ? { reason: options.reason } : {}, ...options.replacementSlug ? { replacementSlug: options.replacementSlug } : {} })
|
|
50229
|
+
});
|
|
50230
|
+
if (!response.ok) {
|
|
50231
|
+
const code = await readResponseCode(response);
|
|
50232
|
+
throw new RemoteSkillLifecycleError(path, response.status, code);
|
|
50233
|
+
}
|
|
50234
|
+
return response.json();
|
|
50235
|
+
}
|
|
50178
50236
|
async downloadSkillBundle(slug2) {
|
|
50179
50237
|
return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}/bundle`, { method: "GET" });
|
|
50180
50238
|
}
|
|
@@ -50326,9 +50384,13 @@ function normalizeSkillSummaryList(payload) {
|
|
|
50326
50384
|
return payload.map(normalizeSkillSummary);
|
|
50327
50385
|
}
|
|
50328
50386
|
async function responseBodyCarriesCode(response, codes) {
|
|
50387
|
+
const code = await readResponseCode(response);
|
|
50388
|
+
return code !== undefined && codes.includes(code);
|
|
50389
|
+
}
|
|
50390
|
+
async function readResponseCode(response) {
|
|
50329
50391
|
const reader = response.body?.getReader();
|
|
50330
50392
|
if (!reader)
|
|
50331
|
-
return
|
|
50393
|
+
return;
|
|
50332
50394
|
const maximum = 8 * 1024;
|
|
50333
50395
|
let deadline;
|
|
50334
50396
|
const expired = new Promise((_, reject) => {
|
|
@@ -50343,7 +50405,7 @@ async function responseBodyCarriesCode(response, codes) {
|
|
|
50343
50405
|
break;
|
|
50344
50406
|
size += next.value.byteLength;
|
|
50345
50407
|
if (size > maximum)
|
|
50346
|
-
return
|
|
50408
|
+
return;
|
|
50347
50409
|
chunks.push(next.value);
|
|
50348
50410
|
}
|
|
50349
50411
|
const bytes = new Uint8Array(size);
|
|
@@ -50354,11 +50416,11 @@ async function responseBodyCarriesCode(response, codes) {
|
|
|
50354
50416
|
}
|
|
50355
50417
|
const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
50356
50418
|
if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
|
|
50357
|
-
return
|
|
50419
|
+
return;
|
|
50358
50420
|
const code = payload.code;
|
|
50359
|
-
return typeof code === "string" &&
|
|
50421
|
+
return typeof code === "string" && /^[A-Z][A-Z0-9_:-]{0,127}$/.test(code) ? code : undefined;
|
|
50360
50422
|
} catch {
|
|
50361
|
-
return
|
|
50423
|
+
return;
|
|
50362
50424
|
} finally {
|
|
50363
50425
|
clearTimeout(deadline);
|
|
50364
50426
|
reader.cancel().catch(() => {});
|
|
@@ -50387,7 +50449,7 @@ async function createRemoteSkillsClient(env = process.env) {
|
|
|
50387
50449
|
function createRemoteSkillsClientReadOnly(env = process.env) {
|
|
50388
50450
|
return createRemoteSkillsClient(env);
|
|
50389
50451
|
}
|
|
50390
|
-
var RemoteRouteUnsupportedError, RemoteRequestError, RemoteCreditCheckoutError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
50452
|
+
var RemoteRouteUnsupportedError, RemoteRequestError, KNOWN_SKILL_LIFECYCLE_CODES, RemoteSkillLifecycleError, RemoteCreditCheckoutError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
50391
50453
|
var init_remote_client = __esm(() => {
|
|
50392
50454
|
init_remote_recurring();
|
|
50393
50455
|
init_remote_invitations();
|
|
@@ -50424,6 +50486,17 @@ var init_remote_client = __esm(() => {
|
|
|
50424
50486
|
this.name = "RemoteRequestError";
|
|
50425
50487
|
}
|
|
50426
50488
|
};
|
|
50489
|
+
KNOWN_SKILL_LIFECYCLE_CODES = new Set(["SKILL_ARCHIVE_PROFILE_CONFLICT", "LIFECYCLE_ROLE_REQUIRED"]);
|
|
50490
|
+
RemoteSkillLifecycleError = class RemoteSkillLifecycleError extends RemoteRequestError {
|
|
50491
|
+
code;
|
|
50492
|
+
constructor(path, status, code) {
|
|
50493
|
+
super(path, status);
|
|
50494
|
+
this.name = "RemoteSkillLifecycleError";
|
|
50495
|
+
const safeCode = code !== undefined && KNOWN_SKILL_LIFECYCLE_CODES.has(code) ? code : undefined;
|
|
50496
|
+
this.code = safeCode;
|
|
50497
|
+
this.message = `Skill lifecycle update was refused (HTTP ${status}${safeCode ? `, code ${safeCode}` : ""})`;
|
|
50498
|
+
}
|
|
50499
|
+
};
|
|
50427
50500
|
RemoteCreditCheckoutError = class RemoteCreditCheckoutError extends RemoteRequestError {
|
|
50428
50501
|
code;
|
|
50429
50502
|
requestIdempotencyKey;
|
|
@@ -53818,6 +53891,161 @@ var init_run_routing = __esm(() => {
|
|
|
53818
53891
|
init_fleet_credentials();
|
|
53819
53892
|
});
|
|
53820
53893
|
|
|
53894
|
+
// src/lib/cloud-executions.ts
|
|
53895
|
+
var exports_cloud_executions = {};
|
|
53896
|
+
__export(exports_cloud_executions, {
|
|
53897
|
+
CloudExecutionClient: () => CloudExecutionClient
|
|
53898
|
+
});
|
|
53899
|
+
|
|
53900
|
+
class CloudExecutionClient {
|
|
53901
|
+
apiOrigin;
|
|
53902
|
+
apiKey;
|
|
53903
|
+
constructor(apiOrigin, apiKey) {
|
|
53904
|
+
this.apiOrigin = apiOrigin;
|
|
53905
|
+
this.apiKey = apiKey;
|
|
53906
|
+
}
|
|
53907
|
+
static async configured() {
|
|
53908
|
+
const c = await resolveSkillsConnection();
|
|
53909
|
+
if (!c)
|
|
53910
|
+
throw Error("Cloud execution requires a configured Skills API credential");
|
|
53911
|
+
return new CloudExecutionClient(c.apiOrigin, c.apiKey);
|
|
53912
|
+
}
|
|
53913
|
+
async request(route, method = "GET", value) {
|
|
53914
|
+
const response = await fetch(skillsApiRequestUrl(this.apiOrigin, "/api/v1/executions/" + route), {
|
|
53915
|
+
method,
|
|
53916
|
+
headers: {
|
|
53917
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
53918
|
+
"User-Agent": `hasna-skills/${package_default.version}`,
|
|
53919
|
+
...value === undefined ? {} : { "content-type": "application/json" }
|
|
53920
|
+
},
|
|
53921
|
+
...value === undefined ? {} : { body: JSON.stringify(value) },
|
|
53922
|
+
redirect: "error",
|
|
53923
|
+
signal: AbortSignal.timeout(15000)
|
|
53924
|
+
});
|
|
53925
|
+
if (!response.ok) {
|
|
53926
|
+
response.body?.cancel();
|
|
53927
|
+
throw Error(`Skills cloud execution refused: HTTP ${response.status}`);
|
|
53928
|
+
}
|
|
53929
|
+
return response;
|
|
53930
|
+
}
|
|
53931
|
+
async eligibility(slug2, version2, bundleDigest) {
|
|
53932
|
+
if (!/^[a-z0-9-]+$/.test(slug2) || !/^\d+\.\d+\.\d+$/.test(version2) || bundleDigest !== undefined && !/^(sha256:)?[a-f0-9]{64}$/.test(bundleDigest))
|
|
53933
|
+
throw Error("Cloud eligibility requires an exact skill version and valid digest");
|
|
53934
|
+
const query = new URLSearchParams({ version: version2, ...bundleDigest ? { bundleDigest } : {} });
|
|
53935
|
+
const v = await jsonBody(await this.request(`${slug2}/eligibility?${query}`));
|
|
53936
|
+
if (!v || v.contractVersion !== 1 || typeof v.eligible !== "boolean" || v.skill !== slug2 || v.version !== version2 || !/^[a-f0-9]{64}$/.test(v.bundleDigest) || bundleDigest && bundleDigest.replace(/^sha256:/, "") !== v.bundleDigest || v.eligible && (!v.executionContract || !["pdf.v1", "regex-test.v1"].includes(v.executionContract.id)))
|
|
53937
|
+
throw Error("Invalid cloud eligibility receipt");
|
|
53938
|
+
return v;
|
|
53939
|
+
}
|
|
53940
|
+
async submit(slug2, version2, input2, key, selection) {
|
|
53941
|
+
if (!/^[a-z0-9-]+$/.test(slug2) || !/^\d+\.\d+\.\d+$/.test(version2))
|
|
53942
|
+
throw Error("Cloud execution requires skill@exact-version");
|
|
53943
|
+
if (!/^[A-Za-z0-9._:-]{1,128}$/.test(key))
|
|
53944
|
+
throw Error("Invalid idempotency key");
|
|
53945
|
+
if (selection?.authority && selection.authority !== skillsApiRequestUrl(this.apiOrigin, "/api/v1/").replace(/\/$/, ""))
|
|
53946
|
+
throw Error("Selected skill belongs to another Skills authority");
|
|
53947
|
+
const execution = parseExecution(await jsonBody(await this.request(slug2, "POST", {
|
|
53948
|
+
version: version2,
|
|
53949
|
+
input: input2,
|
|
53950
|
+
idempotencyKey: key,
|
|
53951
|
+
...selection ? {
|
|
53952
|
+
bundleDigest: selection.bundleDigest,
|
|
53953
|
+
workspaceId: selection.workspaceId
|
|
53954
|
+
} : {}
|
|
53955
|
+
})));
|
|
53956
|
+
if (execution.skill !== slug2 || execution.version !== version2 || selection && execution.bundleDigest.replace(/^sha256:/, "") !== selection.bundleDigest.replace(/^sha256:/, ""))
|
|
53957
|
+
throw Error("Cloud execution receipt does not match the selected exact skill");
|
|
53958
|
+
return execution;
|
|
53959
|
+
}
|
|
53960
|
+
async get(id) {
|
|
53961
|
+
return parseExecution(await jsonBody(await this.request(runId(id))));
|
|
53962
|
+
}
|
|
53963
|
+
async logs(id) {
|
|
53964
|
+
return jsonBody(await this.request(runId(id) + "/logs"));
|
|
53965
|
+
}
|
|
53966
|
+
async artifacts(id) {
|
|
53967
|
+
return (await this.get(id)).artifacts;
|
|
53968
|
+
}
|
|
53969
|
+
async download(id, name) {
|
|
53970
|
+
if (!["document.pdf", "document.html"].includes(name))
|
|
53971
|
+
throw Error("Invalid cloud artifact name");
|
|
53972
|
+
const response = await this.request(runId(id) + "/artifacts/" + encodeURIComponent(name));
|
|
53973
|
+
if (!response.body)
|
|
53974
|
+
throw Error("Cloud artifact body is missing");
|
|
53975
|
+
const reader = response.body.getReader();
|
|
53976
|
+
const chunks = [];
|
|
53977
|
+
let size = 0;
|
|
53978
|
+
for (;; ) {
|
|
53979
|
+
const r2 = await reader.read();
|
|
53980
|
+
if (r2.done)
|
|
53981
|
+
break;
|
|
53982
|
+
size += r2.value.length;
|
|
53983
|
+
if (size > 2000000) {
|
|
53984
|
+
await reader.cancel();
|
|
53985
|
+
throw Error("Cloud artifact exceeds limit");
|
|
53986
|
+
}
|
|
53987
|
+
chunks.push(r2.value);
|
|
53988
|
+
}
|
|
53989
|
+
return Buffer.concat(chunks);
|
|
53990
|
+
}
|
|
53991
|
+
async cancel(id) {
|
|
53992
|
+
return parseExecution(await jsonBody(await this.request(runId(id) + "/cancel", "POST", {})));
|
|
53993
|
+
}
|
|
53994
|
+
async wait(id, options = {}) {
|
|
53995
|
+
const timeout = options.timeoutMs ?? 300000, interval = options.intervalMs ?? 1000;
|
|
53996
|
+
if (!Number.isFinite(timeout) || timeout < 1 || timeout > 1200000 || !Number.isFinite(interval) || interval < 100 || interval > 60000)
|
|
53997
|
+
throw Error("Invalid cloud polling bounds");
|
|
53998
|
+
const until = Date.now() + timeout;
|
|
53999
|
+
for (;; ) {
|
|
54000
|
+
const run = await this.get(id);
|
|
54001
|
+
if (["succeeded", "failed", "cancelled"].includes(run.status))
|
|
54002
|
+
return run;
|
|
54003
|
+
if (Date.now() >= until)
|
|
54004
|
+
throw Error("Cloud execution is still running; poll the returned execution id");
|
|
54005
|
+
await Bun.sleep(interval);
|
|
54006
|
+
}
|
|
54007
|
+
}
|
|
54008
|
+
}
|
|
54009
|
+
function runId(id) {
|
|
54010
|
+
if (!/^run_[a-z0-9_]+$/.test(id))
|
|
54011
|
+
throw Error("Invalid cloud execution id");
|
|
54012
|
+
return id;
|
|
54013
|
+
}
|
|
54014
|
+
function parseExecution(value) {
|
|
54015
|
+
const v = value;
|
|
54016
|
+
if (!v || v.contractVersion !== 1 || v.target !== "cloud" || ![
|
|
54017
|
+
"admitted",
|
|
54018
|
+
"leased",
|
|
54019
|
+
"running",
|
|
54020
|
+
"succeeded",
|
|
54021
|
+
"failed",
|
|
54022
|
+
"cancelled"
|
|
54023
|
+
].includes(v.status) || typeof v.skill !== "string" || typeof v.version !== "string" || !Array.isArray(v.artifacts))
|
|
54024
|
+
throw Error("Unsupported cloud execution response");
|
|
54025
|
+
runId(v.id);
|
|
54026
|
+
for (const field of [
|
|
54027
|
+
"bundleDigest",
|
|
54028
|
+
"inputDigest",
|
|
54029
|
+
"runtimeImageDigest"
|
|
54030
|
+
])
|
|
54031
|
+
if (typeof v[field] !== "string" || !/^(sha256:)?[a-f0-9]{64}$/.test(v[field]))
|
|
54032
|
+
throw Error("Invalid cloud execution integrity receipt");
|
|
54033
|
+
return v;
|
|
54034
|
+
}
|
|
54035
|
+
async function jsonBody(response) {
|
|
54036
|
+
const bytes = await readBoundedResponse(response, 1e5);
|
|
54037
|
+
try {
|
|
54038
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
54039
|
+
} catch {
|
|
54040
|
+
throw Error("Invalid cloud execution JSON response");
|
|
54041
|
+
}
|
|
54042
|
+
}
|
|
54043
|
+
var init_cloud_executions = __esm(() => {
|
|
54044
|
+
init_package();
|
|
54045
|
+
init_remote_files();
|
|
54046
|
+
init_fleet_credentials();
|
|
54047
|
+
});
|
|
54048
|
+
|
|
53821
54049
|
// node_modules/@hasna/secrets/dist/sdk.js
|
|
53822
54050
|
import { isIP as isIP2 } from "net";
|
|
53823
54051
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -55553,152 +55781,6 @@ var init_selected_run = __esm(() => {
|
|
|
55553
55781
|
init_execution_secrets();
|
|
55554
55782
|
});
|
|
55555
55783
|
|
|
55556
|
-
// src/lib/cloud-executions.ts
|
|
55557
|
-
var exports_cloud_executions = {};
|
|
55558
|
-
__export(exports_cloud_executions, {
|
|
55559
|
-
CloudExecutionClient: () => CloudExecutionClient
|
|
55560
|
-
});
|
|
55561
|
-
|
|
55562
|
-
class CloudExecutionClient {
|
|
55563
|
-
apiOrigin;
|
|
55564
|
-
apiKey;
|
|
55565
|
-
constructor(apiOrigin, apiKey) {
|
|
55566
|
-
this.apiOrigin = apiOrigin;
|
|
55567
|
-
this.apiKey = apiKey;
|
|
55568
|
-
}
|
|
55569
|
-
static async configured() {
|
|
55570
|
-
const c = await resolveSkillsConnection();
|
|
55571
|
-
if (!c)
|
|
55572
|
-
throw Error("Cloud execution requires a configured Skills API credential");
|
|
55573
|
-
return new CloudExecutionClient(c.apiOrigin, c.apiKey);
|
|
55574
|
-
}
|
|
55575
|
-
async request(route, method = "GET", value) {
|
|
55576
|
-
const response = await fetch(skillsApiRequestUrl(this.apiOrigin, "/api/v1/executions/" + route), {
|
|
55577
|
-
method,
|
|
55578
|
-
headers: {
|
|
55579
|
-
authorization: `Bearer ${this.apiKey}`,
|
|
55580
|
-
"User-Agent": `hasna-skills/${package_default.version}`,
|
|
55581
|
-
...value === undefined ? {} : { "content-type": "application/json" }
|
|
55582
|
-
},
|
|
55583
|
-
...value === undefined ? {} : { body: JSON.stringify(value) },
|
|
55584
|
-
redirect: "error",
|
|
55585
|
-
signal: AbortSignal.timeout(15000)
|
|
55586
|
-
});
|
|
55587
|
-
if (!response.ok) {
|
|
55588
|
-
response.body?.cancel();
|
|
55589
|
-
throw Error(`Skills cloud execution refused: HTTP ${response.status}`);
|
|
55590
|
-
}
|
|
55591
|
-
return response;
|
|
55592
|
-
}
|
|
55593
|
-
async submit(slug2, version2, input2, key, selection2) {
|
|
55594
|
-
if (!/^[a-z0-9-]+$/.test(slug2) || !/^\d+\.\d+\.\d+$/.test(version2))
|
|
55595
|
-
throw Error("Cloud execution requires skill@exact-version");
|
|
55596
|
-
if (!/^[A-Za-z0-9._:-]{1,128}$/.test(key))
|
|
55597
|
-
throw Error("Invalid idempotency key");
|
|
55598
|
-
if (selection2?.authority && selection2.authority !== skillsApiRequestUrl(this.apiOrigin, "/api/v1/").replace(/\/$/, ""))
|
|
55599
|
-
throw Error("Selected skill belongs to another Skills authority");
|
|
55600
|
-
const execution = parseExecution(await jsonBody(await this.request(slug2, "POST", {
|
|
55601
|
-
version: version2,
|
|
55602
|
-
input: input2,
|
|
55603
|
-
idempotencyKey: key,
|
|
55604
|
-
...selection2 ? {
|
|
55605
|
-
bundleDigest: selection2.bundleDigest,
|
|
55606
|
-
workspaceId: selection2.workspaceId
|
|
55607
|
-
} : {}
|
|
55608
|
-
})));
|
|
55609
|
-
if (execution.skill !== slug2 || execution.version !== version2 || selection2 && execution.bundleDigest.replace(/^sha256:/, "") !== selection2.bundleDigest.replace(/^sha256:/, ""))
|
|
55610
|
-
throw Error("Cloud execution receipt does not match the selected exact skill");
|
|
55611
|
-
return execution;
|
|
55612
|
-
}
|
|
55613
|
-
async get(id2) {
|
|
55614
|
-
return parseExecution(await jsonBody(await this.request(runId(id2))));
|
|
55615
|
-
}
|
|
55616
|
-
async logs(id2) {
|
|
55617
|
-
return jsonBody(await this.request(runId(id2) + "/logs"));
|
|
55618
|
-
}
|
|
55619
|
-
async artifacts(id2) {
|
|
55620
|
-
return (await this.get(id2)).artifacts;
|
|
55621
|
-
}
|
|
55622
|
-
async download(id2, name) {
|
|
55623
|
-
if (!["document.pdf", "document.html"].includes(name))
|
|
55624
|
-
throw Error("Invalid cloud artifact name");
|
|
55625
|
-
const response = await this.request(runId(id2) + "/artifacts/" + encodeURIComponent(name));
|
|
55626
|
-
if (!response.body)
|
|
55627
|
-
throw Error("Cloud artifact body is missing");
|
|
55628
|
-
const reader = response.body.getReader();
|
|
55629
|
-
const chunks = [];
|
|
55630
|
-
let size = 0;
|
|
55631
|
-
for (;; ) {
|
|
55632
|
-
const r2 = await reader.read();
|
|
55633
|
-
if (r2.done)
|
|
55634
|
-
break;
|
|
55635
|
-
size += r2.value.length;
|
|
55636
|
-
if (size > 2000000) {
|
|
55637
|
-
await reader.cancel();
|
|
55638
|
-
throw Error("Cloud artifact exceeds limit");
|
|
55639
|
-
}
|
|
55640
|
-
chunks.push(r2.value);
|
|
55641
|
-
}
|
|
55642
|
-
return Buffer.concat(chunks);
|
|
55643
|
-
}
|
|
55644
|
-
async cancel(id2) {
|
|
55645
|
-
return parseExecution(await jsonBody(await this.request(runId(id2) + "/cancel", "POST", {})));
|
|
55646
|
-
}
|
|
55647
|
-
async wait(id2, options = {}) {
|
|
55648
|
-
const timeout = options.timeoutMs ?? 300000, interval = options.intervalMs ?? 1000;
|
|
55649
|
-
if (!Number.isFinite(timeout) || timeout < 1 || timeout > 1200000 || !Number.isFinite(interval) || interval < 100 || interval > 60000)
|
|
55650
|
-
throw Error("Invalid cloud polling bounds");
|
|
55651
|
-
const until = Date.now() + timeout;
|
|
55652
|
-
for (;; ) {
|
|
55653
|
-
const run = await this.get(id2);
|
|
55654
|
-
if (["succeeded", "failed", "cancelled"].includes(run.status))
|
|
55655
|
-
return run;
|
|
55656
|
-
if (Date.now() >= until)
|
|
55657
|
-
throw Error("Cloud execution is still running; poll the returned execution id");
|
|
55658
|
-
await Bun.sleep(interval);
|
|
55659
|
-
}
|
|
55660
|
-
}
|
|
55661
|
-
}
|
|
55662
|
-
function runId(id2) {
|
|
55663
|
-
if (!/^run_[a-z0-9_]+$/.test(id2))
|
|
55664
|
-
throw Error("Invalid cloud execution id");
|
|
55665
|
-
return id2;
|
|
55666
|
-
}
|
|
55667
|
-
function parseExecution(value) {
|
|
55668
|
-
const v = value;
|
|
55669
|
-
if (!v || v.contractVersion !== 1 || v.target !== "cloud" || ![
|
|
55670
|
-
"admitted",
|
|
55671
|
-
"leased",
|
|
55672
|
-
"running",
|
|
55673
|
-
"succeeded",
|
|
55674
|
-
"failed",
|
|
55675
|
-
"cancelled"
|
|
55676
|
-
].includes(v.status) || typeof v.skill !== "string" || typeof v.version !== "string" || !Array.isArray(v.artifacts))
|
|
55677
|
-
throw Error("Unsupported cloud execution response");
|
|
55678
|
-
runId(v.id);
|
|
55679
|
-
for (const field of [
|
|
55680
|
-
"bundleDigest",
|
|
55681
|
-
"inputDigest",
|
|
55682
|
-
"runtimeImageDigest"
|
|
55683
|
-
])
|
|
55684
|
-
if (typeof v[field] !== "string" || !/^(sha256:)?[a-f0-9]{64}$/.test(v[field]))
|
|
55685
|
-
throw Error("Invalid cloud execution integrity receipt");
|
|
55686
|
-
return v;
|
|
55687
|
-
}
|
|
55688
|
-
async function jsonBody(response) {
|
|
55689
|
-
const bytes = await readBoundedResponse(response, 1e5);
|
|
55690
|
-
try {
|
|
55691
|
-
return JSON.parse(new TextDecoder().decode(bytes));
|
|
55692
|
-
} catch {
|
|
55693
|
-
throw Error("Invalid cloud execution JSON response");
|
|
55694
|
-
}
|
|
55695
|
-
}
|
|
55696
|
-
var init_cloud_executions = __esm(() => {
|
|
55697
|
-
init_package();
|
|
55698
|
-
init_remote_files();
|
|
55699
|
-
init_fleet_credentials();
|
|
55700
|
-
});
|
|
55701
|
-
|
|
55702
55784
|
// src/lib/blog-article.ts
|
|
55703
55785
|
var exports_blog_article = {};
|
|
55704
55786
|
__export(exports_blog_article, {
|
|
@@ -55833,6 +55915,18 @@ var init_blog_article = __esm(() => {
|
|
|
55833
55915
|
import { existsSync as existsSync15, readdirSync as readdirSync9, statSync as statSync10 } from "fs";
|
|
55834
55916
|
import { join as join24 } from "path";
|
|
55835
55917
|
function registerOperationTools(server) {
|
|
55918
|
+
server.registerTool("cloud_skill_eligibility", {
|
|
55919
|
+
title: "Cloud Skill Eligibility",
|
|
55920
|
+
description: "Inspect the authenticated workspace's exact reviewed cloud contract. Does not execute or admit a skill.",
|
|
55921
|
+
inputSchema: { name: exports_external.string(), version: exports_external.string(), bundleDigest: exports_external.string().optional() }
|
|
55922
|
+
}, async ({ name, version: version2, bundleDigest }) => {
|
|
55923
|
+
try {
|
|
55924
|
+
const { CloudExecutionClient: CloudExecutionClient2 } = await Promise.resolve().then(() => (init_cloud_executions(), exports_cloud_executions));
|
|
55925
|
+
return mcpJson(await (await CloudExecutionClient2.configured()).eligibility(name, version2, bundleDigest));
|
|
55926
|
+
} catch (error61) {
|
|
55927
|
+
return mcpError("CLOUD_ELIGIBILITY_FAILED", error61.message);
|
|
55928
|
+
}
|
|
55929
|
+
});
|
|
55836
55930
|
server.registerTool("scaffold_skill", {
|
|
55837
55931
|
title: "Scaffold Skill",
|
|
55838
55932
|
description: "Create a portable skill folder under ~/.hasna/skills/installed/<name> with SKILL.md, skill.json, AGENTS.md, package.json, and src/index.ts.",
|