@hasna/skills 0.1.68 → 0.1.70
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 +1 -1
- package/bin/index.js +384 -19
- package/bin/mcp.js +19 -5
- package/bin/server.js +209 -194
- package/bin/worker.js +199 -190
- package/dist/admin-contract.d.ts +6189 -0
- package/dist/admin-contract.js +4451 -0
- package/dist/index.js +18 -4
- package/dist/sdk/index.js +581 -223
- package/dist/sdk/runs.d.ts +8 -8
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -448,7 +448,7 @@ skills/ # Public skill contracts and local OSS skills
|
|
|
448
448
|
|
|
449
449
|
| Count | Value | Derived from |
|
|
450
450
|
|---|---|---|
|
|
451
|
-
| Catalog skills |
|
|
451
|
+
| Catalog skills | 87 | `SKILLS.length` (`src/lib/registry-data/`) |
|
|
452
452
|
| Categories | 17 | `CATEGORIES` (`src/lib/registry-types.ts`) |
|
|
453
453
|
| MCP tools | 37 | `tools/list` against a live `buildServer()` |
|
|
454
454
|
|
package/bin/index.js
CHANGED
|
@@ -36860,7 +36860,7 @@ var package_default;
|
|
|
36860
36860
|
var init_package = __esm(() => {
|
|
36861
36861
|
package_default = {
|
|
36862
36862
|
name: "@hasna/skills",
|
|
36863
|
-
version: "0.1.
|
|
36863
|
+
version: "0.1.70",
|
|
36864
36864
|
description: "Skills library for AI coding agents",
|
|
36865
36865
|
type: "module",
|
|
36866
36866
|
bin: {
|
|
@@ -36882,6 +36882,10 @@ var init_package = __esm(() => {
|
|
|
36882
36882
|
"./sdk": {
|
|
36883
36883
|
import: "./dist/sdk/index.js",
|
|
36884
36884
|
types: "./dist/sdk/index.d.ts"
|
|
36885
|
+
},
|
|
36886
|
+
"./admin-contract": {
|
|
36887
|
+
import: "./dist/admin-contract.js",
|
|
36888
|
+
types: "./dist/admin-contract.d.ts"
|
|
36885
36889
|
}
|
|
36886
36890
|
},
|
|
36887
36891
|
files: [
|
|
@@ -36900,14 +36904,15 @@ var init_package = __esm(() => {
|
|
|
36900
36904
|
types: "./dist/index.d.ts",
|
|
36901
36905
|
scripts: {
|
|
36902
36906
|
clean: "rm -rf bin/ dist/",
|
|
36903
|
-
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
36904
|
-
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
36907
|
+
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
36908
|
+
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
36905
36909
|
test: "bun test --timeout 30000",
|
|
36906
36910
|
dev: "bun run ./src/cli/index.tsx",
|
|
36907
36911
|
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
36908
36912
|
"dev:mcp": "bun --watch run ./src/mcp/index.ts",
|
|
36909
36913
|
"dev:server": "bun --watch run ./src/server/index.ts",
|
|
36910
36914
|
"dev:worker": "bun --watch run ./src/server/worker.ts",
|
|
36915
|
+
"docker:check": "bash scripts/docker-build-check.sh",
|
|
36911
36916
|
migrate: "bun run ./src/server/migrate.ts",
|
|
36912
36917
|
typecheck: "tsc --noEmit",
|
|
36913
36918
|
"verify:release": "bun run scripts/release-guard.ts",
|
|
@@ -36940,7 +36945,8 @@ var init_package = __esm(() => {
|
|
|
36940
36945
|
"@types/react": "^18.2.0",
|
|
36941
36946
|
"bun-types": "1.3.14",
|
|
36942
36947
|
"react-devtools-core": "^7.0.1",
|
|
36943
|
-
typescript: "^5"
|
|
36948
|
+
typescript: "^5",
|
|
36949
|
+
yaml: "^2.9.0"
|
|
36944
36950
|
},
|
|
36945
36951
|
dependencies: {
|
|
36946
36952
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
@@ -38316,6 +38322,14 @@ var init_development_tools = __esm(() => {
|
|
|
38316
38322
|
category: "Development Tools",
|
|
38317
38323
|
tags: ["config", "validation", "schema", "linting"]
|
|
38318
38324
|
},
|
|
38325
|
+
{
|
|
38326
|
+
name: "oss-app-two-backend-storage",
|
|
38327
|
+
displayName: "OSS App Two-Backend Storage",
|
|
38328
|
+
description: "Recipe for the Hasna two-backend storage contract: client transport + HTTP store, server PG/SQLite backend, pg-migrations + apply script, fail-closed URL-without-key, bun bins, contract manifest, Dockerfile",
|
|
38329
|
+
category: "Development Tools",
|
|
38330
|
+
tags: ["storage", "backend", "postgresql", "sqlite", "two-backend", "oss-app"],
|
|
38331
|
+
kind: "instruction"
|
|
38332
|
+
},
|
|
38319
38333
|
{
|
|
38320
38334
|
name: "session-inject-monitor",
|
|
38321
38335
|
displayName: "Session Inject Monitor",
|
|
@@ -62354,8 +62368,8 @@ var require_utils = __commonJS((exports, module) => {
|
|
|
62354
62368
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
62355
62369
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
62356
62370
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
62357
|
-
function reescapeHostDelimiters(host,
|
|
62358
|
-
const re =
|
|
62371
|
+
function reescapeHostDelimiters(host, isIP2) {
|
|
62372
|
+
const re = isIP2 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
62359
62373
|
re.lastIndex = 0;
|
|
62360
62374
|
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
62361
62375
|
}
|
|
@@ -62804,7 +62818,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
62804
62818
|
fragment: undefined
|
|
62805
62819
|
};
|
|
62806
62820
|
let malformedAuthorityOrPort = false;
|
|
62807
|
-
let
|
|
62821
|
+
let isIP2 = false;
|
|
62808
62822
|
if (options.reference === "suffix") {
|
|
62809
62823
|
if (options.scheme) {
|
|
62810
62824
|
uri = options.scheme + ":" + uri;
|
|
@@ -62853,9 +62867,9 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
62853
62867
|
if (ipv4result === false) {
|
|
62854
62868
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
62855
62869
|
parsed.host = ipv6result.host.toLowerCase();
|
|
62856
|
-
|
|
62870
|
+
isIP2 = ipv6result.isIPV6;
|
|
62857
62871
|
} else {
|
|
62858
|
-
|
|
62872
|
+
isIP2 = true;
|
|
62859
62873
|
}
|
|
62860
62874
|
}
|
|
62861
62875
|
if (parsed.scheme === undefined && parsed.userinfo === undefined && parsed.host === undefined && parsed.port === undefined && parsed.query === undefined && !parsed.path) {
|
|
@@ -62872,7 +62886,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
62872
62886
|
}
|
|
62873
62887
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
62874
62888
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
62875
|
-
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) &&
|
|
62889
|
+
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
|
|
62876
62890
|
try {
|
|
62877
62891
|
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
62878
62892
|
} catch (e) {
|
|
@@ -62886,7 +62900,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
62886
62900
|
parsed.scheme = unescape(parsed.scheme);
|
|
62887
62901
|
}
|
|
62888
62902
|
if (parsed.host !== undefined) {
|
|
62889
|
-
parsed.host = reescapeHostDelimiters(unescape(parsed.host),
|
|
62903
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP2);
|
|
62890
62904
|
}
|
|
62891
62905
|
}
|
|
62892
62906
|
if (parsed.path) {
|
|
@@ -69902,7 +69916,7 @@ var init_server3 = __esm(() => {
|
|
|
69902
69916
|
server = buildServer();
|
|
69903
69917
|
});
|
|
69904
69918
|
|
|
69905
|
-
// ../../node_modules/.bun/@hono+node-server@1.19.17+
|
|
69919
|
+
// ../../node_modules/.bun/@hono+node-server@1.19.17+2145b681a064c8e9/node_modules/@hono/node-server/dist/index.mjs
|
|
69906
69920
|
import { Readable } from "stream";
|
|
69907
69921
|
import crypto2 from "crypto";
|
|
69908
69922
|
var GlobalRequest, Request, newHeadersFromIncoming = (incoming) => {
|
|
@@ -74195,7 +74209,7 @@ import process12 from "process";
|
|
|
74195
74209
|
// ../../node_modules/.bun/ink@5.2.1+6e93904c7bfe2418/node_modules/ink/build/ink.js
|
|
74196
74210
|
var import_react10 = __toESM(require_react(), 1);
|
|
74197
74211
|
import process11 from "process";
|
|
74198
|
-
// ../../node_modules/.bun/es-toolkit@1.
|
|
74212
|
+
// ../../node_modules/.bun/es-toolkit@1.51.0/node_modules/es-toolkit/dist/function/debounce.mjs
|
|
74199
74213
|
function debounce(func, debounceMs, { signal, edges } = {}) {
|
|
74200
74214
|
let pendingThis = undefined;
|
|
74201
74215
|
let pendingArgs = null;
|
|
@@ -74253,7 +74267,7 @@ function debounce(func, debounceMs, { signal, edges } = {}) {
|
|
|
74253
74267
|
return debounced;
|
|
74254
74268
|
}
|
|
74255
74269
|
|
|
74256
|
-
// ../../node_modules/.bun/es-toolkit@1.
|
|
74270
|
+
// ../../node_modules/.bun/es-toolkit@1.51.0/node_modules/es-toolkit/dist/compat/function/debounce.mjs
|
|
74257
74271
|
function debounce2(func, debounceMs = 0, options = {}) {
|
|
74258
74272
|
if (typeof options !== "object")
|
|
74259
74273
|
options = {};
|
|
@@ -74293,7 +74307,7 @@ function debounce2(func, debounceMs = 0, options = {}) {
|
|
|
74293
74307
|
return debounced;
|
|
74294
74308
|
}
|
|
74295
74309
|
|
|
74296
|
-
// ../../node_modules/.bun/es-toolkit@1.
|
|
74310
|
+
// ../../node_modules/.bun/es-toolkit@1.51.0/node_modules/es-toolkit/dist/compat/function/throttle.mjs
|
|
74297
74311
|
function throttle(func, throttleMs = 0, options = {}) {
|
|
74298
74312
|
const { leading = true, trailing = true } = options;
|
|
74299
74313
|
return debounce2(func, throttleMs, {
|
|
@@ -76209,10 +76223,10 @@ var import_react_reconciler = __toESM(require_react_reconciler(), 1);
|
|
|
76209
76223
|
var import_constants = __toESM(require_constants(), 1);
|
|
76210
76224
|
import process3 from "process";
|
|
76211
76225
|
|
|
76212
|
-
// ../../node_modules/.bun/ansi-regex@6.
|
|
76226
|
+
// ../../node_modules/.bun/ansi-regex@6.3.0/node_modules/ansi-regex/index.js
|
|
76213
76227
|
function ansiRegex({ onlyFirst = false } = {}) {
|
|
76214
76228
|
const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
|
|
76215
|
-
const osc = `(?:\\u001B\\][\\
|
|
76229
|
+
const osc = `(?:\\u001B\\][^\\u0007\\u001B\\u009C]*${ST})`;
|
|
76216
76230
|
const csi = "[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
|
|
76217
76231
|
const pattern = `${osc}|${csi}`;
|
|
76218
76232
|
return new RegExp(pattern, onlyFirst ? undefined : "g");
|
|
@@ -79215,8 +79229,12 @@ import { existsSync as existsSync2 } from "fs";
|
|
|
79215
79229
|
import { homedir } from "os";
|
|
79216
79230
|
import { join as join2 } from "path";
|
|
79217
79231
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
79232
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
79233
|
+
import { isIP } from "net";
|
|
79218
79234
|
import { randomUUID } from "crypto";
|
|
79219
79235
|
import { spawn } from "child_process";
|
|
79236
|
+
import { request as nodeHttpRequest } from "http";
|
|
79237
|
+
import { request as nodeHttpsRequest } from "https";
|
|
79220
79238
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
79221
79239
|
function getPathValue(input, path) {
|
|
79222
79240
|
return path.split(".").reduce((value, part) => {
|
|
@@ -79619,6 +79637,214 @@ function signPayload(secret, timestamp, body) {
|
|
|
79619
79637
|
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
79620
79638
|
return `sha256=${digest}`;
|
|
79621
79639
|
}
|
|
79640
|
+
var DEFAULT_MAX_REDIRECTS = 5;
|
|
79641
|
+
var IPV4_PRIVATE_RANGES = [
|
|
79642
|
+
[0, 16777215],
|
|
79643
|
+
[167772160, 184549375],
|
|
79644
|
+
[1681915904, 1686110207],
|
|
79645
|
+
[2130706432, 2147483647],
|
|
79646
|
+
[2851995648, 2852061183],
|
|
79647
|
+
[2886729728, 2887778303],
|
|
79648
|
+
[3221225472, 3221225727],
|
|
79649
|
+
[3221225984, 3221226239],
|
|
79650
|
+
[3227017984, 3227018239],
|
|
79651
|
+
[3232235520, 3232301055],
|
|
79652
|
+
[3323068416, 3323199487],
|
|
79653
|
+
[3325256704, 3325256959],
|
|
79654
|
+
[3405803776, 3405804031],
|
|
79655
|
+
[3758096384, 4294967295]
|
|
79656
|
+
];
|
|
79657
|
+
var IPV6_SPECIAL_PREFIXES = [
|
|
79658
|
+
{ groups: [0, 0, 0, 0, 0, 0, 0, 0], bits: 128 },
|
|
79659
|
+
{ groups: [0, 0, 0, 0, 0, 0, 0, 1], bits: 128 },
|
|
79660
|
+
{ groups: [0, 0, 0, 0, 0, 65535, 0, 0], bits: 96 },
|
|
79661
|
+
{ groups: [100, 65435, 0, 0, 0, 0, 0, 0], bits: 96 },
|
|
79662
|
+
{ groups: [256, 0, 0, 0, 0, 0, 0, 0], bits: 64 },
|
|
79663
|
+
{ groups: [8193, 0, 0, 0, 0, 0, 0, 0], bits: 32 },
|
|
79664
|
+
{ groups: [8193, 2, 0, 0, 0, 0, 0, 0], bits: 48 },
|
|
79665
|
+
{ groups: [8193, 16, 0, 0, 0, 0, 0, 0], bits: 28 },
|
|
79666
|
+
{ groups: [8193, 3512, 0, 0, 0, 0, 0, 0], bits: 32 },
|
|
79667
|
+
{ groups: [8194, 0, 0, 0, 0, 0, 0, 0], bits: 16 },
|
|
79668
|
+
{ groups: [16383, 0, 0, 0, 0, 0, 0, 0], bits: 20 },
|
|
79669
|
+
{ groups: [64512, 0, 0, 0, 0, 0, 0, 0], bits: 7 },
|
|
79670
|
+
{ groups: [65152, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
|
|
79671
|
+
{ groups: [65216, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
|
|
79672
|
+
{ groups: [65280, 0, 0, 0, 0, 0, 0, 0], bits: 8 }
|
|
79673
|
+
];
|
|
79674
|
+
function isPrivateAddress(address) {
|
|
79675
|
+
const normalized = stripZoneId(address);
|
|
79676
|
+
const version = isIP(normalized);
|
|
79677
|
+
if (version === 4) {
|
|
79678
|
+
const integer = ipv4ToInt(normalized);
|
|
79679
|
+
if (integer === undefined)
|
|
79680
|
+
return true;
|
|
79681
|
+
return IPV4_PRIVATE_RANGES.some(([low, high]) => integer >= low && integer <= high);
|
|
79682
|
+
}
|
|
79683
|
+
if (version === 6) {
|
|
79684
|
+
const groups = ipv6Groups(normalized);
|
|
79685
|
+
if (!groups)
|
|
79686
|
+
return true;
|
|
79687
|
+
for (const prefix of IPV6_SPECIAL_PREFIXES) {
|
|
79688
|
+
if (!ipv6MatchesPrefix(groups, prefix.groups, prefix.bits))
|
|
79689
|
+
continue;
|
|
79690
|
+
if (prefix.bits === 96 && groups[5] === 65535) {
|
|
79691
|
+
return isPrivateAddress(ipv4IntToString(groups[6] << 16 | groups[7]));
|
|
79692
|
+
}
|
|
79693
|
+
if (prefix.bits === 16 && groups[0] === 8194) {
|
|
79694
|
+
return isPrivateAddress(ipv4IntToString(groups[1] << 16 | groups[2]));
|
|
79695
|
+
}
|
|
79696
|
+
return true;
|
|
79697
|
+
}
|
|
79698
|
+
return false;
|
|
79699
|
+
}
|
|
79700
|
+
return true;
|
|
79701
|
+
}
|
|
79702
|
+
async function resolveWebhookTarget(url, policy = {}) {
|
|
79703
|
+
const hostname = normalizeHostname(url.hostname);
|
|
79704
|
+
const allowlist = (policy.allowPrivateHosts ?? []).map((entry) => normalizeHostname(entry.toLowerCase()));
|
|
79705
|
+
if (allowlist.includes(hostname)) {
|
|
79706
|
+
const version2 = isIP(hostname);
|
|
79707
|
+
if (version2 === 4 || version2 === 6) {
|
|
79708
|
+
return { hostname, addresses: [hostname] };
|
|
79709
|
+
}
|
|
79710
|
+
const lookup2 = policy.lookup ?? defaultTargetLookup;
|
|
79711
|
+
let resolved2;
|
|
79712
|
+
try {
|
|
79713
|
+
resolved2 = await lookup2(hostname);
|
|
79714
|
+
} catch {
|
|
79715
|
+
throw new Error(`Webhook target ${hostname} could not be resolved`);
|
|
79716
|
+
}
|
|
79717
|
+
if (!Array.isArray(resolved2) || resolved2.length === 0) {
|
|
79718
|
+
throw new Error(`Webhook target ${hostname} resolved to no addresses`);
|
|
79719
|
+
}
|
|
79720
|
+
const addresses = resolved2.map((entry) => normalizeHostname(entry.address));
|
|
79721
|
+
return { hostname, addresses };
|
|
79722
|
+
}
|
|
79723
|
+
const version = isIP(hostname);
|
|
79724
|
+
if (version === 4 || version === 6) {
|
|
79725
|
+
if (isPrivateAddress(hostname)) {
|
|
79726
|
+
throw new Error(`Webhook target ${hostname} is a private or special-use address`);
|
|
79727
|
+
}
|
|
79728
|
+
return { hostname, addresses: [hostname] };
|
|
79729
|
+
}
|
|
79730
|
+
const lookup = policy.lookup ?? defaultTargetLookup;
|
|
79731
|
+
let resolved;
|
|
79732
|
+
try {
|
|
79733
|
+
resolved = await lookup(hostname);
|
|
79734
|
+
} catch {
|
|
79735
|
+
throw new Error(`Webhook target ${hostname} could not be resolved`);
|
|
79736
|
+
}
|
|
79737
|
+
if (!Array.isArray(resolved) || resolved.length === 0) {
|
|
79738
|
+
throw new Error(`Webhook target ${hostname} resolved to no addresses`);
|
|
79739
|
+
}
|
|
79740
|
+
const allowed = [];
|
|
79741
|
+
for (const entry of resolved) {
|
|
79742
|
+
const address = normalizeHostname(entry.address);
|
|
79743
|
+
if (isPrivateAddress(address)) {
|
|
79744
|
+
if (allowlist.includes(address)) {
|
|
79745
|
+
allowed.push(address);
|
|
79746
|
+
continue;
|
|
79747
|
+
}
|
|
79748
|
+
throw new Error(`Webhook target ${hostname} resolves to private or special-use address ${address}`);
|
|
79749
|
+
}
|
|
79750
|
+
allowed.push(address);
|
|
79751
|
+
}
|
|
79752
|
+
if (allowed.length === 0) {
|
|
79753
|
+
throw new Error(`Webhook target ${hostname} resolved to no public addresses`);
|
|
79754
|
+
}
|
|
79755
|
+
return { hostname, addresses: allowed };
|
|
79756
|
+
}
|
|
79757
|
+
function normalizeMaxRedirects(value) {
|
|
79758
|
+
if (value === undefined)
|
|
79759
|
+
return DEFAULT_MAX_REDIRECTS;
|
|
79760
|
+
if (!Number.isInteger(value) || value < 0)
|
|
79761
|
+
throw new Error("webhookTargetPolicy.maxRedirects must be a non-negative integer");
|
|
79762
|
+
return value;
|
|
79763
|
+
}
|
|
79764
|
+
var defaultTargetLookup = async (hostname) => {
|
|
79765
|
+
return dnsLookup(hostname, { all: true, verbatim: false });
|
|
79766
|
+
};
|
|
79767
|
+
function normalizeHostname(hostname) {
|
|
79768
|
+
const lower = hostname.toLowerCase();
|
|
79769
|
+
if (lower.startsWith("[") && lower.endsWith("]"))
|
|
79770
|
+
return lower.slice(1, -1);
|
|
79771
|
+
return lower;
|
|
79772
|
+
}
|
|
79773
|
+
function stripZoneId(address) {
|
|
79774
|
+
const percent = address.indexOf("%");
|
|
79775
|
+
return percent === -1 ? address : address.slice(0, percent);
|
|
79776
|
+
}
|
|
79777
|
+
function ipv4ToInt(address) {
|
|
79778
|
+
const parts = address.split(".");
|
|
79779
|
+
if (parts.length !== 4)
|
|
79780
|
+
return;
|
|
79781
|
+
let value = 0;
|
|
79782
|
+
for (const part of parts) {
|
|
79783
|
+
if (!/^\d{1,3}$/.test(part))
|
|
79784
|
+
return;
|
|
79785
|
+
const octet = Number(part);
|
|
79786
|
+
if (octet > 255)
|
|
79787
|
+
return;
|
|
79788
|
+
value = value << 8 | octet;
|
|
79789
|
+
}
|
|
79790
|
+
return value >>> 0;
|
|
79791
|
+
}
|
|
79792
|
+
function ipv4IntToString(integer) {
|
|
79793
|
+
return [
|
|
79794
|
+
integer >>> 24 & 255,
|
|
79795
|
+
integer >>> 16 & 255,
|
|
79796
|
+
integer >>> 8 & 255,
|
|
79797
|
+
integer & 255
|
|
79798
|
+
].join(".");
|
|
79799
|
+
}
|
|
79800
|
+
function ipv6Groups(address) {
|
|
79801
|
+
const raw = stripZoneId(address);
|
|
79802
|
+
const doubleColon = raw.indexOf("::");
|
|
79803
|
+
const headText = doubleColon === -1 ? raw : raw.slice(0, doubleColon);
|
|
79804
|
+
const tailText = doubleColon === -1 ? "" : raw.slice(doubleColon + 2);
|
|
79805
|
+
const parseGroups = (text) => {
|
|
79806
|
+
if (text === "")
|
|
79807
|
+
return [];
|
|
79808
|
+
const out = [];
|
|
79809
|
+
for (const part of text.split(":")) {
|
|
79810
|
+
if (part.includes(".")) {
|
|
79811
|
+
const v4 = ipv4ToInt(part);
|
|
79812
|
+
if (v4 === undefined)
|
|
79813
|
+
return;
|
|
79814
|
+
out.push(v4 >>> 16 & 65535, v4 & 65535);
|
|
79815
|
+
} else {
|
|
79816
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(part))
|
|
79817
|
+
return;
|
|
79818
|
+
out.push(parseInt(part, 16));
|
|
79819
|
+
}
|
|
79820
|
+
}
|
|
79821
|
+
return out;
|
|
79822
|
+
};
|
|
79823
|
+
const head2 = parseGroups(headText);
|
|
79824
|
+
if (!head2)
|
|
79825
|
+
return;
|
|
79826
|
+
const tail2 = parseGroups(tailText);
|
|
79827
|
+
if (!tail2)
|
|
79828
|
+
return;
|
|
79829
|
+
const total = head2.length + tail2.length;
|
|
79830
|
+
if (doubleColon === -1) {
|
|
79831
|
+
return total === 8 ? head2 : undefined;
|
|
79832
|
+
}
|
|
79833
|
+
if (total >= 8)
|
|
79834
|
+
return;
|
|
79835
|
+
return [...head2, ...new Array(8 - total).fill(0), ...tail2];
|
|
79836
|
+
}
|
|
79837
|
+
function ipv6MatchesPrefix(groups, prefixGroups, prefixBits) {
|
|
79838
|
+
let remaining = prefixBits;
|
|
79839
|
+
for (let index = 0;index < prefixGroups.length && remaining > 0; index += 1) {
|
|
79840
|
+
const take2 = Math.min(16, remaining);
|
|
79841
|
+
const mask = 65535 << 16 - take2 & 65535;
|
|
79842
|
+
if ((groups[index] & mask) !== (prefixGroups[index] & mask))
|
|
79843
|
+
return false;
|
|
79844
|
+
remaining -= take2;
|
|
79845
|
+
}
|
|
79846
|
+
return true;
|
|
79847
|
+
}
|
|
79622
79848
|
function now2() {
|
|
79623
79849
|
return new Date().toISOString();
|
|
79624
79850
|
}
|
|
@@ -79649,9 +79875,18 @@ function buildWebhookRequest(event, channel, options = {}) {
|
|
|
79649
79875
|
}
|
|
79650
79876
|
return { body, headers };
|
|
79651
79877
|
}
|
|
79878
|
+
function normalizeWebhookUrl(raw) {
|
|
79879
|
+
const url = new URL(raw);
|
|
79880
|
+
if (url.username !== "" || url.password !== "") {
|
|
79881
|
+
url.username = "";
|
|
79882
|
+
url.password = "";
|
|
79883
|
+
}
|
|
79884
|
+
return url.toString();
|
|
79885
|
+
}
|
|
79652
79886
|
async function dispatchWebhook(event, channel, options = {}) {
|
|
79653
79887
|
if (!channel.webhook)
|
|
79654
79888
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
79889
|
+
const webhookUrl = normalizeWebhookUrl(channel.webhook.url);
|
|
79655
79890
|
const startedAt = now2();
|
|
79656
79891
|
let secret = channel.webhook.secret;
|
|
79657
79892
|
if (channel.webhook.secretRef) {
|
|
@@ -79668,10 +79903,14 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
79668
79903
|
}
|
|
79669
79904
|
const timestamp = (options.now?.() ?? new Date).toISOString();
|
|
79670
79905
|
const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
|
|
79906
|
+
const validateTargets = options.webhookTargetPolicy !== undefined || options.fetchImpl === undefined;
|
|
79907
|
+
if (validateTargets) {
|
|
79908
|
+
return dispatchValidatedWebhook(event, channel, { body, headers, startedAt, options });
|
|
79909
|
+
}
|
|
79671
79910
|
const controller = new AbortController;
|
|
79672
79911
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
79673
79912
|
try {
|
|
79674
|
-
const response = await (options.fetchImpl ?? fetch)(
|
|
79913
|
+
const response = await (options.fetchImpl ?? fetch)(webhookUrl, {
|
|
79675
79914
|
method: "POST",
|
|
79676
79915
|
headers,
|
|
79677
79916
|
body,
|
|
@@ -79699,6 +79938,130 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
79699
79938
|
clearTimeout(timeout);
|
|
79700
79939
|
}
|
|
79701
79940
|
}
|
|
79941
|
+
function isRedirectStatus(status) {
|
|
79942
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
79943
|
+
}
|
|
79944
|
+
function redirectKeepsBody(status) {
|
|
79945
|
+
return status === 307 || status === 308;
|
|
79946
|
+
}
|
|
79947
|
+
async function pinnedNativeRequest(target, addresses, method2, headers, body, signal, tls) {
|
|
79948
|
+
const isHttps = target.protocol === "https:";
|
|
79949
|
+
if (!isHttps && target.protocol !== "http:") {
|
|
79950
|
+
throw new Error(`Webhook target uses unsupported protocol ${target.protocol}`);
|
|
79951
|
+
}
|
|
79952
|
+
const defaultPort = isHttps ? 443 : 80;
|
|
79953
|
+
const port = target.port ? Number(target.port) : defaultPort;
|
|
79954
|
+
const requestOptions = {
|
|
79955
|
+
hostname: target.hostname,
|
|
79956
|
+
port,
|
|
79957
|
+
path: `${target.pathname}${target.search}`,
|
|
79958
|
+
method: method2,
|
|
79959
|
+
headers,
|
|
79960
|
+
...tls?.ca ? { ca: tls.ca } : {},
|
|
79961
|
+
lookup: (hostname, _options, callback) => {
|
|
79962
|
+
const entries = addresses.map((address) => ({
|
|
79963
|
+
address,
|
|
79964
|
+
family: address.includes(":") ? 6 : 4
|
|
79965
|
+
}));
|
|
79966
|
+
callback(null, entries);
|
|
79967
|
+
}
|
|
79968
|
+
};
|
|
79969
|
+
return new Promise((resolve, reject2) => {
|
|
79970
|
+
const request = isHttps ? nodeHttpsRequest(requestOptions, onResponse) : nodeHttpRequest(requestOptions, onResponse);
|
|
79971
|
+
const onAbort = () => {
|
|
79972
|
+
const error = new Error("The operation was aborted.");
|
|
79973
|
+
error.name = "AbortError";
|
|
79974
|
+
request.destroy(error);
|
|
79975
|
+
};
|
|
79976
|
+
if (signal.aborted)
|
|
79977
|
+
onAbort();
|
|
79978
|
+
else
|
|
79979
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
79980
|
+
request.on("error", reject2);
|
|
79981
|
+
if (body !== undefined)
|
|
79982
|
+
request.write(body);
|
|
79983
|
+
request.end();
|
|
79984
|
+
function onResponse(response) {
|
|
79985
|
+
const chunks = [];
|
|
79986
|
+
response.on("data", (chunk2) => chunks.push(Buffer.from(chunk2)));
|
|
79987
|
+
response.on("error", reject2);
|
|
79988
|
+
response.on("end", () => {
|
|
79989
|
+
const headersRecord = {};
|
|
79990
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
79991
|
+
if (typeof value === "string")
|
|
79992
|
+
headersRecord[name] = value;
|
|
79993
|
+
else if (Array.isArray(value))
|
|
79994
|
+
headersRecord[name] = value.join(", ");
|
|
79995
|
+
}
|
|
79996
|
+
resolve(new Response(Buffer.concat(chunks), { status: response.statusCode ?? 200, headers: headersRecord }));
|
|
79997
|
+
});
|
|
79998
|
+
}
|
|
79999
|
+
});
|
|
80000
|
+
}
|
|
80001
|
+
async function dispatchValidatedWebhook(event, channel, input) {
|
|
80002
|
+
const { body, headers, startedAt, options } = input;
|
|
80003
|
+
const webhook = channel.webhook;
|
|
80004
|
+
if (!webhook)
|
|
80005
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
80006
|
+
const policy = options.webhookTargetPolicy ?? {};
|
|
80007
|
+
const maxRedirects = normalizeMaxRedirects(policy.maxRedirects);
|
|
80008
|
+
const controller = new AbortController;
|
|
80009
|
+
const timeout = setTimeout(() => controller.abort(), webhook.timeoutMs ?? 15000);
|
|
80010
|
+
try {
|
|
80011
|
+
let target = new URL(normalizeWebhookUrl(webhook.url));
|
|
80012
|
+
let requestHeaders = headers;
|
|
80013
|
+
let method2 = "POST";
|
|
80014
|
+
let requestBody = body;
|
|
80015
|
+
let redirectsFollowed = 0;
|
|
80016
|
+
for (;; ) {
|
|
80017
|
+
const resolved = await resolveWebhookTarget(target, policy).catch((error) => {
|
|
80018
|
+
throw new Error(`Webhook target rejected by SSRF guard: ${error.message}`);
|
|
80019
|
+
});
|
|
80020
|
+
const response = options.fetchImpl ? await options.fetchImpl(target, {
|
|
80021
|
+
method: method2,
|
|
80022
|
+
headers: requestHeaders,
|
|
80023
|
+
body: requestBody,
|
|
80024
|
+
signal: controller.signal,
|
|
80025
|
+
redirect: "manual"
|
|
80026
|
+
}) : await pinnedNativeRequest(target, resolved.addresses, method2, requestHeaders, requestBody, controller.signal, options.tls);
|
|
80027
|
+
const location = response.headers.get("location");
|
|
80028
|
+
if (isRedirectStatus(response.status) && location) {
|
|
80029
|
+
if (redirectsFollowed >= maxRedirects) {
|
|
80030
|
+
return failedAttempt(startedAt, `Webhook target exceeded ${maxRedirects} redirects`);
|
|
80031
|
+
}
|
|
80032
|
+
redirectsFollowed += 1;
|
|
80033
|
+
const next = new URL(location, target);
|
|
80034
|
+
target = next;
|
|
80035
|
+
if (!redirectKeepsBody(response.status)) {
|
|
80036
|
+
method2 = "GET";
|
|
80037
|
+
requestBody = undefined;
|
|
80038
|
+
requestHeaders = Object.fromEntries(Object.entries(requestHeaders).filter(([name]) => name.toLowerCase() !== "content-type" && name.toLowerCase() !== "content-length"));
|
|
80039
|
+
}
|
|
80040
|
+
continue;
|
|
80041
|
+
}
|
|
80042
|
+
const responseBody = truncate2(await response.text());
|
|
80043
|
+
return {
|
|
80044
|
+
attempt: 1,
|
|
80045
|
+
status: response.ok ? "success" : "failed",
|
|
80046
|
+
startedAt,
|
|
80047
|
+
completedAt: now2(),
|
|
80048
|
+
responseStatus: response.status,
|
|
80049
|
+
responseBody,
|
|
80050
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
80051
|
+
};
|
|
80052
|
+
}
|
|
80053
|
+
} catch (error) {
|
|
80054
|
+
return {
|
|
80055
|
+
attempt: 1,
|
|
80056
|
+
status: "failed",
|
|
80057
|
+
startedAt,
|
|
80058
|
+
completedAt: now2(),
|
|
80059
|
+
error: error instanceof Error ? error.message : String(error)
|
|
80060
|
+
};
|
|
80061
|
+
} finally {
|
|
80062
|
+
clearTimeout(timeout);
|
|
80063
|
+
}
|
|
80064
|
+
}
|
|
79702
80065
|
function failedAttempt(startedAt, error) {
|
|
79703
80066
|
return {
|
|
79704
80067
|
attempt: 1,
|
|
@@ -79908,7 +80271,9 @@ class EventsClient {
|
|
|
79908
80271
|
this.transportOptions = {
|
|
79909
80272
|
fetchImpl: options.fetchImpl,
|
|
79910
80273
|
secretResolver: options.secretResolver,
|
|
79911
|
-
now: options.now
|
|
80274
|
+
now: options.now,
|
|
80275
|
+
tls: options.tls,
|
|
80276
|
+
webhookTargetPolicy: options.webhookTargetPolicy
|
|
79912
80277
|
};
|
|
79913
80278
|
this.catalog = options.catalog ?? defaultEventTypeCatalog;
|
|
79914
80279
|
this.validateCatalogTypes = options.validateCatalogTypes ?? false;
|
package/bin/mcp.js
CHANGED
|
@@ -12943,7 +12943,7 @@ class StdioServerTransport {
|
|
|
12943
12943
|
// package.json
|
|
12944
12944
|
var package_default = {
|
|
12945
12945
|
name: "@hasna/skills",
|
|
12946
|
-
version: "0.1.
|
|
12946
|
+
version: "0.1.70",
|
|
12947
12947
|
description: "Skills library for AI coding agents",
|
|
12948
12948
|
type: "module",
|
|
12949
12949
|
bin: {
|
|
@@ -12965,6 +12965,10 @@ var package_default = {
|
|
|
12965
12965
|
"./sdk": {
|
|
12966
12966
|
import: "./dist/sdk/index.js",
|
|
12967
12967
|
types: "./dist/sdk/index.d.ts"
|
|
12968
|
+
},
|
|
12969
|
+
"./admin-contract": {
|
|
12970
|
+
import: "./dist/admin-contract.js",
|
|
12971
|
+
types: "./dist/admin-contract.d.ts"
|
|
12968
12972
|
}
|
|
12969
12973
|
},
|
|
12970
12974
|
files: [
|
|
@@ -12983,14 +12987,15 @@ var package_default = {
|
|
|
12983
12987
|
types: "./dist/index.d.ts",
|
|
12984
12988
|
scripts: {
|
|
12985
12989
|
clean: "rm -rf bin/ dist/",
|
|
12986
|
-
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
12987
|
-
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
12990
|
+
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
12991
|
+
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
12988
12992
|
test: "bun test --timeout 30000",
|
|
12989
12993
|
dev: "bun run ./src/cli/index.tsx",
|
|
12990
12994
|
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
12991
12995
|
"dev:mcp": "bun --watch run ./src/mcp/index.ts",
|
|
12992
12996
|
"dev:server": "bun --watch run ./src/server/index.ts",
|
|
12993
12997
|
"dev:worker": "bun --watch run ./src/server/worker.ts",
|
|
12998
|
+
"docker:check": "bash scripts/docker-build-check.sh",
|
|
12994
12999
|
migrate: "bun run ./src/server/migrate.ts",
|
|
12995
13000
|
typecheck: "tsc --noEmit",
|
|
12996
13001
|
"verify:release": "bun run scripts/release-guard.ts",
|
|
@@ -13023,7 +13028,8 @@ var package_default = {
|
|
|
13023
13028
|
"@types/react": "^18.2.0",
|
|
13024
13029
|
"bun-types": "1.3.14",
|
|
13025
13030
|
"react-devtools-core": "^7.0.1",
|
|
13026
|
-
typescript: "^5"
|
|
13031
|
+
typescript: "^5",
|
|
13032
|
+
yaml: "^2.9.0"
|
|
13027
13033
|
},
|
|
13028
13034
|
dependencies: {
|
|
13029
13035
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
@@ -21104,6 +21110,14 @@ var DEVELOPMENT_TOOLS_SKILLS = [
|
|
|
21104
21110
|
category: "Development Tools",
|
|
21105
21111
|
tags: ["config", "validation", "schema", "linting"]
|
|
21106
21112
|
},
|
|
21113
|
+
{
|
|
21114
|
+
name: "oss-app-two-backend-storage",
|
|
21115
|
+
displayName: "OSS App Two-Backend Storage",
|
|
21116
|
+
description: "Recipe for the Hasna two-backend storage contract: client transport + HTTP store, server PG/SQLite backend, pg-migrations + apply script, fail-closed URL-without-key, bun bins, contract manifest, Dockerfile",
|
|
21117
|
+
category: "Development Tools",
|
|
21118
|
+
tags: ["storage", "backend", "postgresql", "sqlite", "two-backend", "oss-app"],
|
|
21119
|
+
kind: "instruction"
|
|
21120
|
+
},
|
|
21107
21121
|
{
|
|
21108
21122
|
name: "session-inject-monitor",
|
|
21109
21123
|
displayName: "Session Inject Monitor",
|
|
@@ -26944,7 +26958,7 @@ var server = buildServer();
|
|
|
26944
26958
|
// src/mcp/http.ts
|
|
26945
26959
|
import { createServer } from "http";
|
|
26946
26960
|
|
|
26947
|
-
// ../../node_modules/.bun/@hono+node-server@1.19.17+
|
|
26961
|
+
// ../../node_modules/.bun/@hono+node-server@1.19.17+2145b681a064c8e9/node_modules/@hono/node-server/dist/index.mjs
|
|
26948
26962
|
import { Http2ServerRequest as Http2ServerRequest2, constants as h2constants } from "http2";
|
|
26949
26963
|
import { Http2ServerRequest } from "http2";
|
|
26950
26964
|
import { Readable } from "stream";
|