@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/README.md
CHANGED
|
@@ -592,13 +592,20 @@ before submitting again. The selected profile must include this exact version,
|
|
|
592
592
|
and the consumer needs `runs:write` as well as `skills:read`.
|
|
593
593
|
|
|
594
594
|
Cloud execution is enabled only when the deployment configures a reviewed image
|
|
595
|
-
and exact bundle allowlist
|
|
596
|
-
uploaded code is not admitted.
|
|
595
|
+
and exact bundle allowlist; arbitrary
|
|
596
|
+
uploaded code is not admitted. The PDF lane accepts reviewed `pdf-generate` versions.
|
|
597
|
+
The optional `regex-test.v1` pure contract accepts only a tenant-specific, reviewed
|
|
598
|
+
self-contained Bun bundle, with no secrets, dependencies or network. Uploading or
|
|
599
|
+
selecting a bundle never admits it to cloud execution. Check an exact version with
|
|
600
|
+
`skills executions eligibility <skill> --skill-version <version> --json`; this
|
|
601
|
+
reads metadata without creating a run. Runs capture version, bundle digest, input digest,
|
|
597
602
|
runtime image digest, limits and policy. The cloud worker runs in a separate
|
|
598
603
|
Fargate task; the skill process has no API/provider credentials, no network, a
|
|
599
604
|
read-only root and bounded temporary storage, execution time and output.
|
|
600
605
|
`GET /skills/v1/capabilities` reports whether this deployment has cloud execution
|
|
601
|
-
configured
|
|
606
|
+
configured and lists supported `cloudExecutionContracts`. Each exact bundle needs
|
|
607
|
+
its own review and admission; the flag does not promise arbitrary executable
|
|
608
|
+
support. Authorization and runtime availability are checked separately.
|
|
602
609
|
|
|
603
610
|
On a managed station, local execution also resolves the selected immutable
|
|
604
611
|
bundle. Self-contained local executables run with explicit environment references
|
|
@@ -1433,7 +1440,7 @@ src/
|
|
|
1433
1440
|
|---|---|---|
|
|
1434
1441
|
| Catalog skills | 0 | `SKILLS.length` (`src/lib/registry-data/`) |
|
|
1435
1442
|
| Categories | 17 | `CATEGORIES` (`src/lib/registry-types.ts`) |
|
|
1436
|
-
| MCP tools |
|
|
1443
|
+
| MCP tools | 82 | `tools/list` against a live `buildServer()` |
|
|
1437
1444
|
|
|
1438
1445
|
Every number in this table is re-derived from the source tree on each test run by
|
|
1439
1446
|
`src/lib/readme-derived-counts.test.ts`, so a drifted figure fails a test rather
|
package/bin/index.js
CHANGED
|
@@ -36868,7 +36868,7 @@ var package_default;
|
|
|
36868
36868
|
var init_package = __esm(() => {
|
|
36869
36869
|
package_default = {
|
|
36870
36870
|
name: "@hasna/skills",
|
|
36871
|
-
version: "0.
|
|
36871
|
+
version: "0.9.1",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -39186,6 +39186,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
|
|
|
39186
39186
|
const version = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
|
|
39187
39187
|
const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
|
|
39188
39188
|
const commands = parseManifestCommands(jsonManifest) ?? (kind === "instruction" ? [] : inferPackageCommands(pkg, name)) ?? [];
|
|
39189
|
+
const runtime = parseManifestRuntime(jsonManifest) ?? inferRuntimeFromCommands(commands);
|
|
39189
39190
|
return {
|
|
39190
39191
|
$schema: stringField(jsonManifest, "$schema") ?? PORTABLE_SKILL_SCHEMA,
|
|
39191
39192
|
standard: stringField(jsonManifest, "standard") ?? PORTABLE_SKILL_STANDARD,
|
|
@@ -39198,7 +39199,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
|
|
|
39198
39199
|
...kind ? { kind } : {},
|
|
39199
39200
|
inputs: kind === "instruction" ? parseManifestInputs(jsonManifest) ?? [] : parseManifestInputs(jsonManifest) ?? DEFAULT_INPUTS,
|
|
39200
39201
|
commands,
|
|
39201
|
-
...
|
|
39202
|
+
...runtime ? { runtime } : {},
|
|
39202
39203
|
...parseManifestProvenance(jsonManifest) ? { provenance: parseManifestProvenance(jsonManifest) } : {}
|
|
39203
39204
|
};
|
|
39204
39205
|
}
|
|
@@ -39717,6 +39718,12 @@ function inferPackageCommands(pkg, fallbackName) {
|
|
|
39717
39718
|
}
|
|
39718
39719
|
return;
|
|
39719
39720
|
}
|
|
39721
|
+
function inferRuntimeFromCommands(commands) {
|
|
39722
|
+
const entrypoint = commands.find((command) => command.entry)?.entry;
|
|
39723
|
+
if (!entrypoint || !entrypoint.toLowerCase().endsWith(".py"))
|
|
39724
|
+
return;
|
|
39725
|
+
return { ...defaultRuntimeContract(entrypoint), runtime: "python3" };
|
|
39726
|
+
}
|
|
39720
39727
|
function readJsonObject(path) {
|
|
39721
39728
|
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
39722
39729
|
if (!isRecord2(parsed))
|
|
@@ -76206,6 +76213,8 @@ function normalizeRemoteSkill(skill) {
|
|
|
76206
76213
|
tags: skill.tags || ["remote"],
|
|
76207
76214
|
dependencies: skill.dependencies,
|
|
76208
76215
|
...skill.version ? { version: skill.version } : {},
|
|
76216
|
+
...skill.revisionId !== undefined ? { revisionId: skill.revisionId } : {},
|
|
76217
|
+
...skill.lifecycle ? { lifecycle: skill.lifecycle } : {},
|
|
76209
76218
|
availability: normalizeRemoteAvailability(skill.availability),
|
|
76210
76219
|
source: "remote"
|
|
76211
76220
|
};
|
|
@@ -76323,6 +76332,8 @@ var init_remote_registry = __esm(() => {
|
|
|
76323
76332
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
76324
76333
|
dependencies: exports_external.array(exports_external.string()).optional(),
|
|
76325
76334
|
version: exports_external.string().optional(),
|
|
76335
|
+
revisionId: exports_external.union([exports_external.string().regex(/^[\x21-\x7e]+$/), exports_external.null()]).optional(),
|
|
76336
|
+
lifecycle: exports_external.enum(["active", "archived"]).optional(),
|
|
76326
76337
|
availability: remoteAvailabilitySchema.optional()
|
|
76327
76338
|
}).passthrough().refine((skill) => skill.name || skill.slug, {
|
|
76328
76339
|
message: "Remote skill requires name or slug"
|
|
@@ -77711,6 +77722,7 @@ __export(exports_remote_client, {
|
|
|
77711
77722
|
RemoteWorkspaceSelectionError: () => RemoteWorkspaceSelectionError,
|
|
77712
77723
|
RemoteWorkspaceMemberError: () => RemoteWorkspaceMemberError,
|
|
77713
77724
|
RemoteSkillsClient: () => RemoteSkillsClient,
|
|
77725
|
+
RemoteSkillLifecycleError: () => RemoteSkillLifecycleError,
|
|
77714
77726
|
RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
|
|
77715
77727
|
RemoteRequestError: () => RemoteRequestError,
|
|
77716
77728
|
RemoteQuoteUnavailableError: () => RemoteQuoteUnavailableError,
|
|
@@ -77842,6 +77854,39 @@ class RemoteSkillsClient {
|
|
|
77842
77854
|
})();
|
|
77843
77855
|
return this.capabilities;
|
|
77844
77856
|
}
|
|
77857
|
+
async addSkillPublishScope(keyId, expectedScopes, expectedOrgId) {
|
|
77858
|
+
if (!/^[A-Za-z0-9_-]+$/.test(keyId))
|
|
77859
|
+
throw new Error("Invalid API key id");
|
|
77860
|
+
if (!/^[A-Za-z0-9_-]{1,256}$/.test(expectedOrgId))
|
|
77861
|
+
throw new Error("Invalid expected organization id");
|
|
77862
|
+
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)
|
|
77863
|
+
throw new Error("Invalid expected API key scopes");
|
|
77864
|
+
const path = `/api/v1/admin/keys/${encodeURIComponent(keyId)}/scopes`;
|
|
77865
|
+
const response = await this.request(path, { method: "PATCH", body: JSON.stringify({ expected_scopes: expectedScopes, add_scopes: ["skills:publish"] }) });
|
|
77866
|
+
if (!response.ok)
|
|
77867
|
+
throw new RemoteRequestError(path, response.status);
|
|
77868
|
+
let value;
|
|
77869
|
+
try {
|
|
77870
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 64 * 1024)));
|
|
77871
|
+
} catch {
|
|
77872
|
+
throw new Error("Invalid API key scope update response");
|
|
77873
|
+
}
|
|
77874
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
77875
|
+
throw new Error("Invalid API key scope update response");
|
|
77876
|
+
const body = value;
|
|
77877
|
+
const returnedId = body.keyId;
|
|
77878
|
+
const orgId = body.orgId;
|
|
77879
|
+
const scopes = body.scopes;
|
|
77880
|
+
if (returnedId !== keyId || typeof orgId !== "string" || !orgId || expectedOrgId !== undefined && orgId !== expectedOrgId || !Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string") || body.updated !== true) {
|
|
77881
|
+
throw new Error("Invalid API key scope update response");
|
|
77882
|
+
}
|
|
77883
|
+
const returnedScopes = scopes;
|
|
77884
|
+
const expectedResult = expectedScopes.includes("skills:publish") ? expectedScopes : [...expectedScopes, "skills:publish"];
|
|
77885
|
+
if (returnedScopes.length !== expectedResult.length || expectedResult.some((scope, index) => returnedScopes[index] !== scope)) {
|
|
77886
|
+
throw new Error("API key scope update response did not preserve the expected scopes");
|
|
77887
|
+
}
|
|
77888
|
+
return { keyId, orgId, scopes: [...returnedScopes], updated: true };
|
|
77889
|
+
}
|
|
77845
77890
|
async submitQuotedRun(slug2, input2 = {}, args = [], approval = {}) {
|
|
77846
77891
|
runQuoteReceipt(approval.quoteReceipt);
|
|
77847
77892
|
({ input: input2, args, approval } = JSON.parse(JSON.stringify({ input: input2, args, approval })));
|
|
@@ -78359,6 +78404,19 @@ class RemoteSkillsClient {
|
|
|
78359
78404
|
async deleteSkill(slug2) {
|
|
78360
78405
|
return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}`, { method: "DELETE" });
|
|
78361
78406
|
}
|
|
78407
|
+
async setSkillLifecycle(slug2, lifecycle, options) {
|
|
78408
|
+
const path = `/api/v1/skills/${encodeURIComponent(slug2)}/lifecycle`;
|
|
78409
|
+
const response = await this.request(path, {
|
|
78410
|
+
method: "PATCH",
|
|
78411
|
+
headers: { "If-Match": options.expectedRevisionId },
|
|
78412
|
+
body: JSON.stringify({ lifecycle, ...options.reason ? { reason: options.reason } : {}, ...options.replacementSlug ? { replacementSlug: options.replacementSlug } : {} })
|
|
78413
|
+
});
|
|
78414
|
+
if (!response.ok) {
|
|
78415
|
+
const code = await readResponseCode(response);
|
|
78416
|
+
throw new RemoteSkillLifecycleError(path, response.status, code);
|
|
78417
|
+
}
|
|
78418
|
+
return response.json();
|
|
78419
|
+
}
|
|
78362
78420
|
async downloadSkillBundle(slug2) {
|
|
78363
78421
|
return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}/bundle`, { method: "GET" });
|
|
78364
78422
|
}
|
|
@@ -78510,9 +78568,13 @@ function normalizeSkillSummaryList(payload) {
|
|
|
78510
78568
|
return payload.map(normalizeSkillSummary);
|
|
78511
78569
|
}
|
|
78512
78570
|
async function responseBodyCarriesCode(response, codes) {
|
|
78571
|
+
const code = await readResponseCode(response);
|
|
78572
|
+
return code !== undefined && codes.includes(code);
|
|
78573
|
+
}
|
|
78574
|
+
async function readResponseCode(response) {
|
|
78513
78575
|
const reader = response.body?.getReader();
|
|
78514
78576
|
if (!reader)
|
|
78515
|
-
return
|
|
78577
|
+
return;
|
|
78516
78578
|
const maximum = 8 * 1024;
|
|
78517
78579
|
let deadline;
|
|
78518
78580
|
const expired = new Promise((_, reject2) => {
|
|
@@ -78527,7 +78589,7 @@ async function responseBodyCarriesCode(response, codes) {
|
|
|
78527
78589
|
break;
|
|
78528
78590
|
size2 += next.value.byteLength;
|
|
78529
78591
|
if (size2 > maximum)
|
|
78530
|
-
return
|
|
78592
|
+
return;
|
|
78531
78593
|
chunks.push(next.value);
|
|
78532
78594
|
}
|
|
78533
78595
|
const bytes = new Uint8Array(size2);
|
|
@@ -78538,11 +78600,11 @@ async function responseBodyCarriesCode(response, codes) {
|
|
|
78538
78600
|
}
|
|
78539
78601
|
const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
78540
78602
|
if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
|
|
78541
|
-
return
|
|
78603
|
+
return;
|
|
78542
78604
|
const code = payload.code;
|
|
78543
|
-
return typeof code === "string" &&
|
|
78605
|
+
return typeof code === "string" && /^[A-Z][A-Z0-9_:-]{0,127}$/.test(code) ? code : undefined;
|
|
78544
78606
|
} catch {
|
|
78545
|
-
return
|
|
78607
|
+
return;
|
|
78546
78608
|
} finally {
|
|
78547
78609
|
clearTimeout(deadline);
|
|
78548
78610
|
reader.cancel().catch(() => {});
|
|
@@ -78571,7 +78633,7 @@ async function createRemoteSkillsClient(env3 = process.env) {
|
|
|
78571
78633
|
function createRemoteSkillsClientReadOnly(env3 = process.env) {
|
|
78572
78634
|
return createRemoteSkillsClient(env3);
|
|
78573
78635
|
}
|
|
78574
|
-
var RemoteRouteUnsupportedError, RemoteRequestError, RemoteCreditCheckoutError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
|
|
78636
|
+
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.";
|
|
78575
78637
|
var init_remote_client = __esm(() => {
|
|
78576
78638
|
init_remote_recurring();
|
|
78577
78639
|
init_remote_invitations();
|
|
@@ -78608,6 +78670,17 @@ var init_remote_client = __esm(() => {
|
|
|
78608
78670
|
this.name = "RemoteRequestError";
|
|
78609
78671
|
}
|
|
78610
78672
|
};
|
|
78673
|
+
KNOWN_SKILL_LIFECYCLE_CODES = new Set(["SKILL_ARCHIVE_PROFILE_CONFLICT", "LIFECYCLE_ROLE_REQUIRED"]);
|
|
78674
|
+
RemoteSkillLifecycleError = class RemoteSkillLifecycleError extends RemoteRequestError {
|
|
78675
|
+
code;
|
|
78676
|
+
constructor(path, status, code) {
|
|
78677
|
+
super(path, status);
|
|
78678
|
+
this.name = "RemoteSkillLifecycleError";
|
|
78679
|
+
const safeCode = code !== undefined && KNOWN_SKILL_LIFECYCLE_CODES.has(code) ? code : undefined;
|
|
78680
|
+
this.code = safeCode;
|
|
78681
|
+
this.message = `Skill lifecycle update was refused (HTTP ${status}${safeCode ? `, code ${safeCode}` : ""})`;
|
|
78682
|
+
}
|
|
78683
|
+
};
|
|
78611
78684
|
RemoteCreditCheckoutError = class RemoteCreditCheckoutError extends RemoteRequestError {
|
|
78612
78685
|
code;
|
|
78613
78686
|
requestIdempotencyKey;
|
|
@@ -78663,7 +78736,7 @@ var init_remote_client = __esm(() => {
|
|
|
78663
78736
|
// src/lib/revision.ts
|
|
78664
78737
|
import { createHash as createHash16 } from "crypto";
|
|
78665
78738
|
function revisionIdOf(content) {
|
|
78666
|
-
const
|
|
78739
|
+
const canonicalContent = {
|
|
78667
78740
|
slug: content.slug,
|
|
78668
78741
|
displayName: content.displayName,
|
|
78669
78742
|
description: content.description,
|
|
@@ -78675,7 +78748,13 @@ function revisionIdOf(content) {
|
|
|
78675
78748
|
skillMd: content.skillMd ?? null,
|
|
78676
78749
|
bundleSha256: content.bundleSha256 ?? null,
|
|
78677
78750
|
bundleByteSize: content.bundleByteSize ?? null
|
|
78678
|
-
}
|
|
78751
|
+
};
|
|
78752
|
+
if (content.lifecycle === "archived" || content.archiveReason !== undefined || content.replacementSlug !== undefined) {
|
|
78753
|
+
canonicalContent.lifecycle = content.lifecycle ?? "active";
|
|
78754
|
+
canonicalContent.archiveReason = content.archiveReason ?? null;
|
|
78755
|
+
canonicalContent.replacementSlug = content.replacementSlug ?? null;
|
|
78756
|
+
}
|
|
78757
|
+
const canonical4 = JSON.stringify(canonicalContent);
|
|
78679
78758
|
return createHash16("sha256").update(canonical4).digest("hex");
|
|
78680
78759
|
}
|
|
78681
78760
|
var REVISION_ID_PATTERN;
|
|
@@ -82131,6 +82210,15 @@ class CloudExecutionClient {
|
|
|
82131
82210
|
}
|
|
82132
82211
|
return response;
|
|
82133
82212
|
}
|
|
82213
|
+
async eligibility(slug2, version2, bundleDigest) {
|
|
82214
|
+
if (!/^[a-z0-9-]+$/.test(slug2) || !/^\d+\.\d+\.\d+$/.test(version2) || bundleDigest !== undefined && !/^(sha256:)?[a-f0-9]{64}$/.test(bundleDigest))
|
|
82215
|
+
throw Error("Cloud eligibility requires an exact skill version and valid digest");
|
|
82216
|
+
const query = new URLSearchParams({ version: version2, ...bundleDigest ? { bundleDigest } : {} });
|
|
82217
|
+
const v = await jsonBody(await this.request(`${slug2}/eligibility?${query}`));
|
|
82218
|
+
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)))
|
|
82219
|
+
throw Error("Invalid cloud eligibility receipt");
|
|
82220
|
+
return v;
|
|
82221
|
+
}
|
|
82134
82222
|
async submit(slug2, version2, input2, key, selection) {
|
|
82135
82223
|
if (!/^[a-z0-9-]+$/.test(slug2) || !/^\d+\.\d+\.\d+$/.test(version2))
|
|
82136
82224
|
throw Error("Cloud execution requires skill@exact-version");
|
|
@@ -103202,6 +103290,18 @@ var init_run_routing = __esm(() => {
|
|
|
103202
103290
|
import { existsSync as existsSync28, readdirSync as readdirSync17, statSync as statSync16 } from "fs";
|
|
103203
103291
|
import { join as join46 } from "path";
|
|
103204
103292
|
function registerOperationTools(server) {
|
|
103293
|
+
server.registerTool("cloud_skill_eligibility", {
|
|
103294
|
+
title: "Cloud Skill Eligibility",
|
|
103295
|
+
description: "Inspect the authenticated workspace's exact reviewed cloud contract. Does not execute or admit a skill.",
|
|
103296
|
+
inputSchema: { name: exports_external.string(), version: exports_external.string(), bundleDigest: exports_external.string().optional() }
|
|
103297
|
+
}, async ({ name, version: version2, bundleDigest }) => {
|
|
103298
|
+
try {
|
|
103299
|
+
const { CloudExecutionClient: CloudExecutionClient2 } = await Promise.resolve().then(() => (init_cloud_executions(), exports_cloud_executions));
|
|
103300
|
+
return mcpJson(await (await CloudExecutionClient2.configured()).eligibility(name, version2, bundleDigest));
|
|
103301
|
+
} catch (error61) {
|
|
103302
|
+
return mcpError("CLOUD_ELIGIBILITY_FAILED", error61.message);
|
|
103303
|
+
}
|
|
103304
|
+
});
|
|
103205
103305
|
server.registerTool("scaffold_skill", {
|
|
103206
103306
|
title: "Scaffold Skill",
|
|
103207
103307
|
description: "Create a portable skill folder under ~/.hasna/skills/installed/<name> with SKILL.md, skill.json, AGENTS.md, package.json, and src/index.ts.",
|
|
@@ -105518,6 +105618,15 @@ function registerRuntime(parent) {
|
|
|
105518
105618
|
await handleRun(name, args2, options, separatorBeforeSkill);
|
|
105519
105619
|
});
|
|
105520
105620
|
const executions = parent.command("executions").description("Inspect isolated cloud executions");
|
|
105621
|
+
executions.command("eligibility").argument("<skill>").requiredOption("--skill-version <version>", "Exact published version").option("--bundle-digest <digest>", "Expected published bundle SHA256").option("--json", "Output as JSON", false).description("Inspect reviewed cloud eligibility without executing a skill").action(async (slug2, options) => {
|
|
105622
|
+
try {
|
|
105623
|
+
const client2 = await CloudExecutionClient.configured();
|
|
105624
|
+
await writeCliOutput(JSON.stringify(await client2.eligibility(slug2, options.skillVersion, options.bundleDigest), null, 2));
|
|
105625
|
+
} catch (error61) {
|
|
105626
|
+
console.error(JSON.stringify({ error: error61.message }));
|
|
105627
|
+
process.exitCode = 1;
|
|
105628
|
+
}
|
|
105629
|
+
});
|
|
105521
105630
|
for (const operation of ["show", "logs", "artifacts", "cancel"]) {
|
|
105522
105631
|
executions.command(operation).argument("<execution-id>").option("--json", "Output as JSON", false).action(async (id2) => {
|
|
105523
105632
|
try {
|
|
@@ -109219,6 +109328,7 @@ function validateRawCandidate(candidate2) {
|
|
|
109219
109328
|
const issues = validatePortableManifestContract(candidate2, { strict: true });
|
|
109220
109329
|
if (issues.length)
|
|
109221
109330
|
throw new Error(`Invalid skill.json: ${issues.map((issue2) => issue2.code).join(", ")}`);
|
|
109331
|
+
validateRuntimeEntrypointCompatibility(candidate2);
|
|
109222
109332
|
for (const field2 of ["inputs", "commands"]) {
|
|
109223
109333
|
const items = candidate2[field2];
|
|
109224
109334
|
if (items === undefined)
|
|
@@ -109244,6 +109354,18 @@ function validateRawCandidate(candidate2) {
|
|
|
109244
109354
|
}
|
|
109245
109355
|
}
|
|
109246
109356
|
}
|
|
109357
|
+
function validateRuntimeEntrypointCompatibility(candidate2) {
|
|
109358
|
+
if (candidate2.kind !== "executable" || !isRecord6(candidate2.runtime))
|
|
109359
|
+
return;
|
|
109360
|
+
const runtime = candidate2.runtime.runtime;
|
|
109361
|
+
const entrypoint = candidate2.runtime.entrypoint;
|
|
109362
|
+
if (typeof runtime !== "string" || typeof entrypoint !== "string")
|
|
109363
|
+
return;
|
|
109364
|
+
const extension = entrypoint.toLowerCase().split(".").pop();
|
|
109365
|
+
if (extension === "py" && runtime !== "python3") {
|
|
109366
|
+
throw new Error("Invalid skill.json: runtime.entrypoint with .py extension requires runtime python3.");
|
|
109367
|
+
}
|
|
109368
|
+
}
|
|
109247
109369
|
function compareVersions(left, right) {
|
|
109248
109370
|
const parts = (value) => value.split("+")[0].split(/-(.*)/s).slice(0, 2);
|
|
109249
109371
|
const [a, ap] = parts(left), [b, bp] = parts(right);
|
|
@@ -109779,7 +109901,8 @@ var exports_registry2 = {};
|
|
|
109779
109901
|
__export(exports_registry2, {
|
|
109780
109902
|
registerVersions: () => registerVersions,
|
|
109781
109903
|
registerRegistry: () => registerRegistry,
|
|
109782
|
-
registerPull: () => registerPull
|
|
109904
|
+
registerPull: () => registerPull,
|
|
109905
|
+
registerLifecycle: () => registerLifecycle
|
|
109783
109906
|
});
|
|
109784
109907
|
function registerRegistry(parent) {
|
|
109785
109908
|
const registry2 = parent.command("registry").description("Generate registry artifacts for hosted skills services");
|
|
@@ -109935,6 +110058,25 @@ Versions of ${name}
|
|
|
109935
110058
|
}
|
|
109936
110059
|
});
|
|
109937
110060
|
}
|
|
110061
|
+
function registerLifecycle(parent) {
|
|
110062
|
+
parent.command("lifecycle").argument("<name>", "Skill name on the configured instance").requiredOption("--state <state>", "Lifecycle state: active or archived").requiredOption("--revision <revision>", "Exact revision id from skills info/get").option("--reason <reason>", "Bounded archive reason").option("--replacement <slug>", "Optional successor slug").option("--json", "Output as JSON", false).description("Change a hosted skill's active/archive lifecycle with an exact revision guard").action(async (name, options) => {
|
|
110063
|
+
if (options.state !== "active" && options.state !== "archived")
|
|
110064
|
+
throw new Error("--state must be active or archived");
|
|
110065
|
+
const client2 = await createRemoteSkillsClient();
|
|
110066
|
+
if (!client2)
|
|
110067
|
+
throw new Error("No API key configured, so there is no instance to update.");
|
|
110068
|
+
try {
|
|
110069
|
+
const result2 = await client2.setSkillLifecycle(name.trim(), options.state, { expectedRevisionId: options.revision, ...options.reason ? { reason: options.reason } : {}, ...options.replacement ? { replacementSlug: options.replacement } : {} });
|
|
110070
|
+
console.log(options.json ? JSON.stringify(result2, null, 2) : `Updated ${name} to ${options.state}.`);
|
|
110071
|
+
} catch (error61) {
|
|
110072
|
+
if (options.json)
|
|
110073
|
+
console.log(JSON.stringify({ error: error61.message }, null, 2));
|
|
110074
|
+
else
|
|
110075
|
+
console.error(source_default.red(error61.message));
|
|
110076
|
+
process.exitCode = 1;
|
|
110077
|
+
}
|
|
110078
|
+
});
|
|
110079
|
+
}
|
|
109938
110080
|
var init_registry2 = __esm(() => {
|
|
109939
110081
|
init_source();
|
|
109940
110082
|
init_registry_sync();
|
|
@@ -110482,6 +110624,11 @@ function authIdentityPayload(authSource, live, cached2, offline = false) {
|
|
|
110482
110624
|
const userId = stringField2(user?.id) ?? cached2?.userId;
|
|
110483
110625
|
const orgId = stringField2(organization2?.id) ?? cached2?.orgId;
|
|
110484
110626
|
const role3 = stringField2(user?.role);
|
|
110627
|
+
const apiKey = recordField(root2.apiKey) ?? recordField(data?.apiKey);
|
|
110628
|
+
const apiKeyId = stringField2(apiKey?.id);
|
|
110629
|
+
const apiKeyScopes = apiKey?.scopes;
|
|
110630
|
+
const safeApiKeyId = apiKeyId && /^[A-Za-z0-9_-]{1,256}$/.test(apiKeyId) ? apiKeyId : undefined;
|
|
110631
|
+
const safeApiKeyScopes = Array.isArray(apiKeyScopes) && apiKeyScopes.length <= 32 && apiKeyScopes.every((scope) => typeof scope === "string" && scope.length <= 128 && /^(?:\*|[a-z][a-z0-9_-]*:(?:\*|[a-z][a-z0-9_-]*))$/.test(scope)) ? [...new Set(apiKeyScopes)] : undefined;
|
|
110485
110632
|
return {
|
|
110486
110633
|
status: "authenticated",
|
|
110487
110634
|
authSource,
|
|
@@ -110491,7 +110638,8 @@ function authIdentityPayload(authSource, live, cached2, offline = false) {
|
|
|
110491
110638
|
...orgName ? { organizationName: orgName } : {},
|
|
110492
110639
|
...userId ? { userId } : {},
|
|
110493
110640
|
...orgId ? { orgId } : {},
|
|
110494
|
-
...role3 ? { role: role3 } : {}
|
|
110641
|
+
...role3 ? { role: role3 } : {},
|
|
110642
|
+
...safeApiKeyId && safeApiKeyScopes ? { apiKeyId: safeApiKeyId, scopes: safeApiKeyScopes } : {}
|
|
110495
110643
|
};
|
|
110496
110644
|
}
|
|
110497
110645
|
function printWhoami(payload) {
|
|
@@ -110501,6 +110649,8 @@ function printWhoami(payload) {
|
|
|
110501
110649
|
console.log(source_default.bold("Org: ") + payload.organization);
|
|
110502
110650
|
if (payload.role)
|
|
110503
110651
|
console.log(source_default.bold("Role: ") + payload.role);
|
|
110652
|
+
if (payload.apiKeyId)
|
|
110653
|
+
console.log(source_default.bold("Key: ") + payload.apiKeyId);
|
|
110504
110654
|
if (payload.organizationName)
|
|
110505
110655
|
console.log(source_default.bold("Name: ") + payload.organizationName);
|
|
110506
110656
|
if (payload.authSource)
|
|
@@ -110822,6 +110972,25 @@ function registerAuth(parent) {
|
|
|
110822
110972
|
writeCommandError(error61, "Failed to revoke API key", options.json);
|
|
110823
110973
|
}
|
|
110824
110974
|
});
|
|
110975
|
+
keys3.command("add-publish-scope").argument("<key-id>").requiredOption("--expected-scopes <csv>", "Current comma-separated scopes; stale values are refused").option("--json", "Output as JSON", false).description("Add skills:publish to one existing key without minting or rotating it").action(async (id3, options) => {
|
|
110976
|
+
try {
|
|
110977
|
+
const connection = await resolveSkillsConnection();
|
|
110978
|
+
if (!connection)
|
|
110979
|
+
throw new Error("A hosted Skills credential is required for key scope administration");
|
|
110980
|
+
const identity4 = await apiRequest("/api/auth/whoami", { headers: { Authorization: `Bearer ${connection.apiKey}` } }, connection.apiOrigin);
|
|
110981
|
+
const identityOrg = recordField(identity4.organization);
|
|
110982
|
+
const orgId = stringField2(identityOrg?.id);
|
|
110983
|
+
if (!orgId)
|
|
110984
|
+
throw new Error("The Skills authority did not return a tenant identity; refusing key scope administration");
|
|
110985
|
+
const result2 = await new RemoteSkillsClient(connection.apiKey, connection.apiOrigin).addSkillPublishScope(id3, options.expectedScopes.split(",").map((scope) => scope.trim()).filter(Boolean), orgId);
|
|
110986
|
+
if (options.json || !isTTY)
|
|
110987
|
+
console.log(JSON.stringify(result2, null, 2));
|
|
110988
|
+
else
|
|
110989
|
+
console.log(source_default.green(`Added skills:publish to ${id3}; scopes read back from the server.`));
|
|
110990
|
+
} catch (error61) {
|
|
110991
|
+
writeCommandError(error61, "Failed to add Skills publication scope", options.json);
|
|
110992
|
+
}
|
|
110993
|
+
});
|
|
110825
110994
|
auth.command("login").description("Sign in with browser/device code or email code").option("--email <email>", "Email address (non-interactive)").option("--code <code>", "Verification code (non-interactive)").option("--membership-id <id>", "Enroll an exact workspace membership into an explicit HASNA_PROFILE").option("--code-stdin", "Read a fresh six-digit code for workspace enrollment from stdin").option("--api-key <key>", "Verify and store an API key").option("--device", "Use browser/device-code login", false).option("--no-open", "Do not open a browser for device-code login").option("--poll", "Poll until browser authentication completes in non-interactive mode", false).option("--poll-timeout-ms <ms>", "Maximum time to wait for device-code login").option("--json", "Output result as JSON", false).action(async (options) => {
|
|
110826
110995
|
if (options.membershipId !== undefined) {
|
|
110827
110996
|
if (options.apiKey || options.device || options.code || options.poll) {
|
|
@@ -119885,10 +120054,11 @@ var { registerPortableSkillCommands: registerPortableSkillCommands2 } = await Pr
|
|
|
119885
120054
|
registerPortableSkillCommands2(program2);
|
|
119886
120055
|
var { registerSchedule: registerSchedule2 } = await Promise.resolve().then(() => (init_schedule(), exports_schedule));
|
|
119887
120056
|
registerSchedule2(program2);
|
|
119888
|
-
var { registerRegistry: registerRegistry2, registerPull: registerPull2, registerVersions: registerVersions2 } = await Promise.resolve().then(() => (init_registry2(), exports_registry2));
|
|
120057
|
+
var { registerRegistry: registerRegistry2, registerPull: registerPull2, registerVersions: registerVersions2, registerLifecycle: registerLifecycle2 } = await Promise.resolve().then(() => (init_registry2(), exports_registry2));
|
|
119889
120058
|
registerRegistry2(program2);
|
|
119890
120059
|
registerPull2(program2);
|
|
119891
120060
|
registerVersions2(program2);
|
|
120061
|
+
registerLifecycle2(program2);
|
|
119892
120062
|
var { registerPublish: registerPublish2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
|
|
119893
120063
|
registerPublish2(program2);
|
|
119894
120064
|
var { registerPrivatePublications: registerPrivatePublications2 } = await Promise.resolve().then(() => (init_private_publications(), exports_private_publications));
|