@hasna/skills 0.1.44 → 0.1.45

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/dist/index.js CHANGED
@@ -18776,7 +18776,7 @@ import { dirname as dirname4, relative as relative3 } from "path";
18776
18776
  // package.json
18777
18777
  var package_default = {
18778
18778
  name: "@hasna/skills",
18779
- version: "0.1.44",
18779
+ version: "0.1.45",
18780
18780
  description: "Skills library for AI coding agents",
18781
18781
  type: "module",
18782
18782
  bin: {
@@ -18787,6 +18787,10 @@ var package_default = {
18787
18787
  ".": {
18788
18788
  import: "./dist/index.js",
18789
18789
  types: "./dist/index.d.ts"
18790
+ },
18791
+ "./storage": {
18792
+ import: "./dist/storage.js",
18793
+ types: "./dist/storage.d.ts"
18790
18794
  }
18791
18795
  },
18792
18796
  files: [
@@ -18807,7 +18811,7 @@ var package_default = {
18807
18811
  types: "./dist/index.d.ts",
18808
18812
  scripts: {
18809
18813
  clean: "rm -rf bin/ dist/",
18810
- build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
18814
+ 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/index.ts ./src/storage.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
18811
18815
  test: "bun test",
18812
18816
  dev: "bun run ./src/cli/index.tsx",
18813
18817
  "dev:watch": "bun --watch run ./src/cli/index.tsx",
@@ -18837,9 +18841,11 @@ var package_default = {
18837
18841
  devDependencies: {
18838
18842
  "@types/bun": "latest",
18839
18843
  "@types/react": "^18.2.0",
18844
+ "react-devtools-core": "^7.0.1",
18840
18845
  typescript: "^5"
18841
18846
  },
18842
18847
  dependencies: {
18848
+ "@hasna/events": "^0.1.3",
18843
18849
  "@modelcontextprotocol/sdk": "^1.26.0",
18844
18850
  chalk: "^5.3.0",
18845
18851
  commander: "^12.1.0",
@@ -19314,6 +19320,46 @@ var toolContracts = [
19314
19320
  inputSchema: objectSchema(),
19315
19321
  outputSchema: objectSchema({}, [], "Setup summary.", true)
19316
19322
  },
19323
+ {
19324
+ name: "storage_status",
19325
+ title: "Storage Status",
19326
+ description: "Show local-first storage paths and optional repo-owned Postgres/S3 readiness.",
19327
+ params: ["directory?"],
19328
+ category: "storage",
19329
+ sideEffects: "none",
19330
+ stable: true,
19331
+ inputSchema: objectSchema({ directory: stringSchema("Project directory.") }),
19332
+ outputSchema: objectSchema({
19333
+ package: stringSchema("Package name."),
19334
+ mode: { type: "string", enum: ["local", "remote", "hybrid"] },
19335
+ local: objectSchema({}, [], "Local storage paths.", true),
19336
+ remote: objectSchema({}, [], "Remote storage readiness.", true)
19337
+ }, ["package", "mode", "local", "remote"])
19338
+ },
19339
+ {
19340
+ name: "storage_sync_plan",
19341
+ title: "Storage Sync Plan",
19342
+ description: "Plan .skills snapshot sync for optional Postgres/S3 storage without network access.",
19343
+ params: ["directory?", "includeSchemaSql?"],
19344
+ category: "storage",
19345
+ sideEffects: "none",
19346
+ stable: true,
19347
+ inputSchema: objectSchema({
19348
+ directory: stringSchema("Project directory."),
19349
+ includeSchemaSql: { type: "boolean", default: false }
19350
+ }),
19351
+ outputSchema: objectSchema({
19352
+ package: stringSchema("Package name."),
19353
+ noNetwork: { type: "boolean", const: true },
19354
+ mode: { type: "string", enum: ["local", "remote", "hybrid"] },
19355
+ databaseConfigured: { type: "boolean" },
19356
+ s3Configured: { type: "boolean" },
19357
+ snapshotFileCount: { type: "number" },
19358
+ s3ObjectCount: { type: "number" },
19359
+ env: objectSchema({}, [], "Storage env var names.", true),
19360
+ schemaSql: stringSchema("Optional Postgres schema SQL.")
19361
+ }, ["package", "noNetwork", "mode", "databaseConfigured", "s3Configured"])
19362
+ },
19317
19363
  {
19318
19364
  name: "schedule_skill",
19319
19365
  title: "Schedule Skill",
@@ -19666,6 +19712,553 @@ function saveFeedback(input) {
19666
19712
  }
19667
19713
  return { saved: true, category, path: getFeedbackDbPath() };
19668
19714
  }
19715
+ // src/lib/native-storage.ts
19716
+ import { createHash as createHash2, createHmac } from "crypto";
19717
+ import {
19718
+ existsSync as existsSync12,
19719
+ mkdirSync as mkdirSync9,
19720
+ readFileSync as readFileSync11,
19721
+ readdirSync as readdirSync5,
19722
+ statSync as statSync4,
19723
+ writeFileSync as writeFileSync8
19724
+ } from "fs";
19725
+ import { dirname as dirname6, join as join12, normalize as normalize3, relative as relative4, sep } from "path";
19726
+ var SKILLS_STORAGE_TABLES = [
19727
+ "skills_sync_records",
19728
+ "skills_sync_cursors"
19729
+ ];
19730
+ var STORAGE_TABLES = SKILLS_STORAGE_TABLES;
19731
+ var SKILLS_NATIVE_STORAGE_ENV = {
19732
+ mode: "HASNA_SKILLS_STORAGE_MODE",
19733
+ databaseUrl: "HASNA_SKILLS_DATABASE_URL",
19734
+ databaseSsl: "HASNA_SKILLS_DATABASE_SSL",
19735
+ databaseSchema: "HASNA_SKILLS_DATABASE_SCHEMA",
19736
+ s3Bucket: "HASNA_SKILLS_S3_BUCKET",
19737
+ s3Prefix: "HASNA_SKILLS_S3_PREFIX",
19738
+ awsRegion: "HASNA_SKILLS_AWS_REGION",
19739
+ s3Endpoint: "HASNA_SKILLS_S3_ENDPOINT",
19740
+ s3ForcePathStyle: "HASNA_SKILLS_S3_FORCE_PATH_STYLE",
19741
+ s3AccessKeyId: "HASNA_SKILLS_S3_ACCESS_KEY_ID",
19742
+ s3SecretAccessKey: "HASNA_SKILLS_S3_SECRET_ACCESS_KEY",
19743
+ s3SessionToken: "HASNA_SKILLS_S3_SESSION_TOKEN",
19744
+ syncBatchSize: "HASNA_SKILLS_SYNC_BATCH_SIZE",
19745
+ dryRun: "HASNA_SKILLS_SYNC_DRY_RUN"
19746
+ };
19747
+ var SKILLS_NATIVE_STORAGE_FALLBACK_ENV = {
19748
+ mode: "SKILLS_STORAGE_MODE",
19749
+ databaseUrl: "SKILLS_DATABASE_URL",
19750
+ databaseSsl: "SKILLS_DATABASE_SSL",
19751
+ databaseSchema: "SKILLS_DATABASE_SCHEMA",
19752
+ s3Bucket: "SKILLS_S3_BUCKET",
19753
+ s3Prefix: "SKILLS_S3_PREFIX",
19754
+ awsRegion: "SKILLS_AWS_REGION",
19755
+ s3Endpoint: "SKILLS_S3_ENDPOINT",
19756
+ s3ForcePathStyle: "SKILLS_S3_FORCE_PATH_STYLE",
19757
+ s3AccessKeyId: "SKILLS_S3_ACCESS_KEY_ID",
19758
+ s3SecretAccessKey: "SKILLS_S3_SECRET_ACCESS_KEY",
19759
+ s3SessionToken: "SKILLS_S3_SESSION_TOKEN",
19760
+ syncBatchSize: "SKILLS_SYNC_BATCH_SIZE",
19761
+ dryRun: "SKILLS_SYNC_DRY_RUN"
19762
+ };
19763
+ var SKILLS_STORAGE_ENV = SKILLS_NATIVE_STORAGE_ENV;
19764
+ var SKILLS_STORAGE_FALLBACK_ENV = SKILLS_NATIVE_STORAGE_FALLBACK_ENV;
19765
+ function resolveSkillsNativeStorageConfig(env = process.env) {
19766
+ const mode = getSkillsStorageMode(env);
19767
+ return {
19768
+ mode,
19769
+ databaseUrl: getSkillsStorageDatabaseUrl(env),
19770
+ databaseSsl: parseBoolean(readStorageEnv(env, "databaseSsl").value),
19771
+ databaseSchema: readStorageEnv(env, "databaseSchema").value,
19772
+ s3Bucket: readStorageEnv(env, "s3Bucket").value,
19773
+ s3Prefix: readStorageEnv(env, "s3Prefix").value,
19774
+ awsRegion: readStorageEnv(env, "awsRegion").value ?? "us-east-1",
19775
+ s3Endpoint: readStorageEnv(env, "s3Endpoint").value,
19776
+ s3ForcePathStyle: parseBoolean(readStorageEnv(env, "s3ForcePathStyle").value) ?? false,
19777
+ syncBatchSize: parsePositiveInteger(readStorageEnv(env, "syncBatchSize").value) ?? 500,
19778
+ dryRun: parseBoolean(readStorageEnv(env, "dryRun").value) ?? true
19779
+ };
19780
+ }
19781
+ function resolveStorageConfig(env = process.env) {
19782
+ return resolveSkillsNativeStorageConfig(env);
19783
+ }
19784
+ function getSkillsStorageMode(env = process.env) {
19785
+ return parseMode(readStorageEnv(env, "mode").value);
19786
+ }
19787
+ function getStorageMode(env = process.env) {
19788
+ return getSkillsStorageMode(env);
19789
+ }
19790
+ function getSkillsStorageDatabaseEnv(env = process.env) {
19791
+ return readStorageEnv(env, "databaseUrl").name;
19792
+ }
19793
+ function getStorageDatabaseEnv(env = process.env) {
19794
+ return getSkillsStorageDatabaseEnv(env);
19795
+ }
19796
+ function getSkillsStorageDatabaseUrl(env = process.env) {
19797
+ return readStorageEnv(env, "databaseUrl").value;
19798
+ }
19799
+ function getStorageDatabaseUrl(env = process.env) {
19800
+ return getSkillsStorageDatabaseUrl(env);
19801
+ }
19802
+ function getSkillsNativeStorageStatus(options = {}) {
19803
+ const env = options.env ?? process.env;
19804
+ const config2 = resolveSkillsNativeStorageConfig(env);
19805
+ const modeEnv = readStorageEnv(env, "mode");
19806
+ const databaseEnv = readStorageEnv(env, "databaseUrl");
19807
+ const s3BucketEnv = readStorageEnv(env, "s3Bucket");
19808
+ const targetDir = options.targetDir ?? process.cwd();
19809
+ return {
19810
+ package: "open-skills",
19811
+ mode: config2.mode,
19812
+ tables: [...SKILLS_STORAGE_TABLES],
19813
+ env: {
19814
+ mode: SKILLS_NATIVE_STORAGE_ENV.mode,
19815
+ databaseUrl: SKILLS_NATIVE_STORAGE_ENV.databaseUrl,
19816
+ s3Bucket: SKILLS_NATIVE_STORAGE_ENV.s3Bucket
19817
+ },
19818
+ local: {
19819
+ dataDir: getDataDir(),
19820
+ projectStateDir: getProjectStateDir(targetDir),
19821
+ feedbackDbPath: join12(getDataDir(), "skills.db")
19822
+ },
19823
+ remote: {
19824
+ databaseConfigured: Boolean(config2.databaseUrl),
19825
+ s3Configured: Boolean(config2.s3Bucket),
19826
+ databaseEnv: SKILLS_NATIVE_STORAGE_ENV.databaseUrl,
19827
+ s3BucketEnv: SKILLS_NATIVE_STORAGE_ENV.s3Bucket,
19828
+ activeModeEnv: modeEnv.name,
19829
+ activeDatabaseEnv: databaseEnv.name,
19830
+ activeS3BucketEnv: s3BucketEnv.name,
19831
+ region: config2.awsRegion ?? "us-east-1",
19832
+ dryRun: config2.dryRun
19833
+ }
19834
+ };
19835
+ }
19836
+ function getSkillsStorageStatus(options = {}) {
19837
+ return getSkillsNativeStorageStatus(options);
19838
+ }
19839
+ function getStorageStatus(options = {}) {
19840
+ return getSkillsNativeStorageStatus(options);
19841
+ }
19842
+ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
19843
+ const projectStateDir = getProjectStateDir(targetDir);
19844
+ const files = [];
19845
+ if (existsSync12(projectStateDir)) {
19846
+ for (const filePath of walkFiles2(projectStateDir)) {
19847
+ const bytes = readFileSync11(filePath);
19848
+ const relativePath = toPosix(relative4(targetDir, filePath));
19849
+ files.push({
19850
+ path: relativePath,
19851
+ sizeBytes: bytes.byteLength,
19852
+ sha256: createHash2("sha256").update(bytes).digest("hex"),
19853
+ ...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
19854
+ });
19855
+ }
19856
+ }
19857
+ return {
19858
+ schemaVersion: 1,
19859
+ exportedAt: new Date().toISOString(),
19860
+ files: files.sort((a, b) => a.path.localeCompare(b.path))
19861
+ };
19862
+ }
19863
+ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options = {}) {
19864
+ let written = 0;
19865
+ let skipped = 0;
19866
+ for (const file2 of snapshot.files) {
19867
+ if (!file2.contentBase64) {
19868
+ skipped += 1;
19869
+ continue;
19870
+ }
19871
+ const absolutePath = resolveSnapshotPath(targetDir, file2.path);
19872
+ if (existsSync12(absolutePath) && !options.overwrite) {
19873
+ skipped += 1;
19874
+ continue;
19875
+ }
19876
+ const bytes = Buffer.from(file2.contentBase64, "base64");
19877
+ const hash2 = createHash2("sha256").update(bytes).digest("hex");
19878
+ if (hash2 !== file2.sha256) {
19879
+ throw new Error(`Snapshot file checksum mismatch: ${file2.path}`);
19880
+ }
19881
+ mkdirSync9(dirname6(absolutePath), { recursive: true });
19882
+ writeFileSync8(absolutePath, bytes);
19883
+ written += 1;
19884
+ }
19885
+ return { written, skipped };
19886
+ }
19887
+ var skillsPostgresSyncSchemaSql = `
19888
+ CREATE TABLE IF NOT EXISTS skills_sync_records (
19889
+ scope TEXT NOT NULL,
19890
+ kind TEXT NOT NULL,
19891
+ id TEXT NOT NULL,
19892
+ updated_at TIMESTAMPTZ NOT NULL,
19893
+ deleted_at TIMESTAMPTZ,
19894
+ source TEXT,
19895
+ payload JSONB NOT NULL,
19896
+ PRIMARY KEY (scope, kind, id)
19897
+ );
19898
+
19899
+ CREATE INDEX IF NOT EXISTS skills_sync_records_updated_at_idx
19900
+ ON skills_sync_records (updated_at);
19901
+
19902
+ CREATE TABLE IF NOT EXISTS skills_sync_cursors (
19903
+ scope TEXT NOT NULL,
19904
+ cursor_name TEXT NOT NULL,
19905
+ value TEXT NOT NULL,
19906
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
19907
+ PRIMARY KEY (scope, cursor_name)
19908
+ );
19909
+ `.trim();
19910
+
19911
+ class SkillsPostgresSyncStore {
19912
+ client;
19913
+ constructor(client) {
19914
+ this.client = client;
19915
+ }
19916
+ async ensureSchema() {
19917
+ await this.client.query(skillsPostgresSyncSchemaSql);
19918
+ }
19919
+ async upsertRecords(records) {
19920
+ let count = 0;
19921
+ for (const record2 of records) {
19922
+ await this.client.query([
19923
+ "INSERT INTO skills_sync_records",
19924
+ "(scope, kind, id, updated_at, deleted_at, source, payload)",
19925
+ "VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)",
19926
+ "ON CONFLICT (scope, kind, id) DO UPDATE SET",
19927
+ "updated_at = EXCLUDED.updated_at,",
19928
+ "deleted_at = EXCLUDED.deleted_at,",
19929
+ "source = EXCLUDED.source,",
19930
+ "payload = EXCLUDED.payload"
19931
+ ].join(" "), [
19932
+ record2.scope,
19933
+ record2.kind,
19934
+ record2.id,
19935
+ record2.updatedAt,
19936
+ record2.deletedAt ?? null,
19937
+ record2.source ?? null,
19938
+ JSON.stringify(record2.payload)
19939
+ ]);
19940
+ count += 1;
19941
+ }
19942
+ return count;
19943
+ }
19944
+ async pullUpdatedSince(params) {
19945
+ const limit = params.limit ?? 500;
19946
+ const result = await this.client.query([
19947
+ "SELECT scope, kind, id, updated_at, deleted_at, source, payload",
19948
+ "FROM skills_sync_records",
19949
+ "WHERE scope = $1 AND updated_at > $2",
19950
+ "ORDER BY updated_at ASC",
19951
+ "LIMIT $3"
19952
+ ].join(" "), [params.scope, params.since ?? "1970-01-01T00:00:00.000Z", limit]);
19953
+ return result.rows.map((row) => ({
19954
+ scope: row.scope,
19955
+ kind: row.kind,
19956
+ id: row.id,
19957
+ updatedAt: toIsoString(row.updated_at),
19958
+ deletedAt: row.deleted_at ? toIsoString(row.deleted_at) : null,
19959
+ source: row.source,
19960
+ payload: typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload
19961
+ }));
19962
+ }
19963
+ async getCursor(scope, cursorName) {
19964
+ const result = await this.client.query("SELECT value FROM skills_sync_cursors WHERE scope = $1 AND cursor_name = $2", [scope, cursorName]);
19965
+ return result.rows[0]?.value ?? null;
19966
+ }
19967
+ async setCursor(scope, cursorName, value) {
19968
+ await this.client.query([
19969
+ "INSERT INTO skills_sync_cursors (scope, cursor_name, value, updated_at)",
19970
+ "VALUES ($1, $2, $3, now())",
19971
+ "ON CONFLICT (scope, cursor_name) DO UPDATE SET",
19972
+ "value = EXCLUDED.value, updated_at = EXCLUDED.updated_at"
19973
+ ].join(" "), [scope, cursorName, value]);
19974
+ }
19975
+ }
19976
+ function createSkillsPostgresSyncStore(client) {
19977
+ return new SkillsPostgresSyncStore(client);
19978
+ }
19979
+ function createSkillsSnapshotSyncRecord(snapshot, options = {}) {
19980
+ return {
19981
+ scope: options.scope ?? "default",
19982
+ kind: "local-snapshot",
19983
+ id: options.id ?? "project-state",
19984
+ updatedAt: snapshot.exportedAt,
19985
+ source: options.source ?? "open-skills",
19986
+ payload: snapshot
19987
+ };
19988
+ }
19989
+
19990
+ class SkillsS3ObjectStore {
19991
+ options;
19992
+ fetchImpl;
19993
+ region;
19994
+ prefix;
19995
+ constructor(options) {
19996
+ this.options = options;
19997
+ this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
19998
+ this.region = options.region ?? "us-east-1";
19999
+ this.prefix = normalizeS3Prefix(options.prefix);
20000
+ }
20001
+ objectKey(path) {
20002
+ const cleaned = toPosix(path).replace(/^\.?\//, "").replace(/^\/+/, "");
20003
+ return [this.prefix, cleaned].filter(Boolean).join("/");
20004
+ }
20005
+ objectUrl(key) {
20006
+ return buildSkillsS3ObjectUrl({
20007
+ bucket: this.options.bucket,
20008
+ key,
20009
+ region: this.region,
20010
+ endpoint: this.options.endpoint,
20011
+ forcePathStyle: this.options.forcePathStyle
20012
+ });
20013
+ }
20014
+ async putObject(params) {
20015
+ const key = this.objectKey(params.key);
20016
+ const body = typeof params.body === "string" ? new TextEncoder().encode(params.body) : params.body;
20017
+ const url2 = this.objectUrl(key);
20018
+ const headers = {
20019
+ "content-type": params.contentType ?? "application/octet-stream",
20020
+ "x-amz-content-sha256": sha256Hex(body)
20021
+ };
20022
+ const signed = signSkillsAwsV4Request({
20023
+ method: "PUT",
20024
+ url: url2,
20025
+ region: this.region,
20026
+ service: "s3",
20027
+ headers,
20028
+ body,
20029
+ credentials: this.options.credentials
20030
+ });
20031
+ const response = await this.fetchImpl(url2, {
20032
+ method: "PUT",
20033
+ headers: signed.headers,
20034
+ body: toArrayBuffer(body)
20035
+ });
20036
+ if (!response.ok) {
20037
+ throw new Error(`S3 put failed for ${key}: ${response.status} ${response.statusText}`.trim());
20038
+ }
20039
+ return {
20040
+ key,
20041
+ url: url2,
20042
+ etag: response.headers.get("etag"),
20043
+ sizeBytes: body.byteLength
20044
+ };
20045
+ }
20046
+ async getObject(key) {
20047
+ const objectKey = this.objectKey(key);
20048
+ const url2 = this.objectUrl(objectKey);
20049
+ const signed = signSkillsAwsV4Request({
20050
+ method: "GET",
20051
+ url: url2,
20052
+ region: this.region,
20053
+ service: "s3",
20054
+ headers: { "x-amz-content-sha256": sha256Hex(new Uint8Array) },
20055
+ credentials: this.options.credentials
20056
+ });
20057
+ const response = await this.fetchImpl(url2, {
20058
+ method: "GET",
20059
+ headers: signed.headers
20060
+ });
20061
+ if (!response.ok) {
20062
+ throw new Error(`S3 get failed for ${objectKey}: ${response.status} ${response.statusText}`.trim());
20063
+ }
20064
+ return new Uint8Array(await response.arrayBuffer());
20065
+ }
20066
+ }
20067
+ function createSkillsS3ObjectStore(options) {
20068
+ return new SkillsS3ObjectStore(options);
20069
+ }
20070
+ function planSkillsS3SnapshotUpload(snapshot, options = {}) {
20071
+ const prefix = normalizeS3Prefix(options.prefix);
20072
+ return snapshot.files.map((file2) => ({
20073
+ path: file2.path,
20074
+ key: [prefix, file2.path.replace(/^\.?\//, "")].filter(Boolean).join("/"),
20075
+ sizeBytes: file2.sizeBytes,
20076
+ sha256: file2.sha256
20077
+ }));
20078
+ }
20079
+ async function uploadSkillsSnapshotFilesToS3(snapshot, store) {
20080
+ const uploaded = [];
20081
+ for (const file2 of snapshot.files) {
20082
+ if (!file2.contentBase64)
20083
+ continue;
20084
+ uploaded.push(await store.putObject({
20085
+ key: file2.path,
20086
+ body: Buffer.from(file2.contentBase64, "base64"),
20087
+ contentType: contentTypeForPath(file2.path)
20088
+ }));
20089
+ }
20090
+ return uploaded;
20091
+ }
20092
+ function signSkillsAwsV4Request(options) {
20093
+ const now = options.now ?? new Date;
20094
+ const amzDate = toAmzDate(now);
20095
+ const dateStamp = amzDate.slice(0, 8);
20096
+ const url2 = new URL(options.url);
20097
+ const bodyBytes = typeof options.body === "string" ? new TextEncoder().encode(options.body) : options.body ?? new Uint8Array;
20098
+ const payloadHash = sha256Hex(bodyBytes);
20099
+ const headers = normalizeHeaders({
20100
+ ...options.headers ?? {},
20101
+ host: url2.host,
20102
+ "x-amz-date": amzDate,
20103
+ "x-amz-content-sha256": options.headers?.["x-amz-content-sha256"] ?? payloadHash,
20104
+ ...options.credentials.sessionToken ? { "x-amz-security-token": options.credentials.sessionToken } : {}
20105
+ });
20106
+ const signedHeaderNames = Object.keys(headers).sort();
20107
+ const canonicalHeaders = signedHeaderNames.map((name) => `${name}:${headers[name]}
20108
+ `).join("");
20109
+ const canonicalQuery = canonicalizeQuery(url2.searchParams);
20110
+ const canonicalRequest = [
20111
+ options.method.toUpperCase(),
20112
+ encodeUriPath(url2.pathname),
20113
+ canonicalQuery,
20114
+ canonicalHeaders,
20115
+ signedHeaderNames.join(";"),
20116
+ headers["x-amz-content-sha256"]
20117
+ ].join(`
20118
+ `);
20119
+ const credentialScope = `${dateStamp}/${options.region}/${options.service}/aws4_request`;
20120
+ const stringToSign = [
20121
+ "AWS4-HMAC-SHA256",
20122
+ amzDate,
20123
+ credentialScope,
20124
+ sha256Hex(canonicalRequest)
20125
+ ].join(`
20126
+ `);
20127
+ const signingKey = getAwsSigningKey(options.credentials.secretAccessKey, dateStamp, options.region, options.service);
20128
+ const signature = createHmac("sha256", signingKey).update(stringToSign).digest("hex");
20129
+ headers.authorization = [
20130
+ `AWS4-HMAC-SHA256 Credential=${options.credentials.accessKeyId}/${credentialScope}`,
20131
+ `SignedHeaders=${signedHeaderNames.join(";")}`,
20132
+ `Signature=${signature}`
20133
+ ].join(", ");
20134
+ return { headers, canonicalRequest, stringToSign };
20135
+ }
20136
+ function buildSkillsS3ObjectUrl(params) {
20137
+ const region = params.region ?? "us-east-1";
20138
+ const key = params.key.split("/").map(encodeURIComponent).join("/");
20139
+ if (params.endpoint) {
20140
+ const endpoint = params.endpoint.replace(/\/+$/, "");
20141
+ return params.forcePathStyle ? `${endpoint}/${encodeURIComponent(params.bucket)}/${key}` : `${endpoint}/${key}`;
20142
+ }
20143
+ return params.forcePathStyle ? `https://s3.${region}.amazonaws.com/${encodeURIComponent(params.bucket)}/${key}` : `https://${params.bucket}.s3.${region}.amazonaws.com/${key}`;
20144
+ }
20145
+ function parseMode(value) {
20146
+ const normalized = value?.trim().toLowerCase();
20147
+ if (normalized === "remote" || normalized === "hybrid")
20148
+ return normalized;
20149
+ return "local";
20150
+ }
20151
+ function readStorageEnv(env, key) {
20152
+ const primaryName = SKILLS_NATIVE_STORAGE_ENV[key];
20153
+ const primaryValue = cleanOptional(env[primaryName]);
20154
+ if (primaryValue !== undefined)
20155
+ return { name: primaryName, value: primaryValue };
20156
+ const fallbackName = SKILLS_NATIVE_STORAGE_FALLBACK_ENV[key];
20157
+ const fallbackValue = cleanOptional(env[fallbackName]);
20158
+ if (fallbackValue !== undefined)
20159
+ return { name: fallbackName, value: fallbackValue };
20160
+ return { name: primaryName };
20161
+ }
20162
+ function cleanOptional(value) {
20163
+ const cleaned = value?.trim();
20164
+ return cleaned ? cleaned : undefined;
20165
+ }
20166
+ function parseBoolean(value) {
20167
+ if (value === undefined)
20168
+ return;
20169
+ const normalized = value.trim().toLowerCase();
20170
+ if (["1", "true", "yes", "on"].includes(normalized))
20171
+ return true;
20172
+ if (["0", "false", "no", "off"].includes(normalized))
20173
+ return false;
20174
+ return;
20175
+ }
20176
+ function parsePositiveInteger(value) {
20177
+ if (!value)
20178
+ return;
20179
+ const parsed = Number.parseInt(value, 10);
20180
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
20181
+ }
20182
+ function walkFiles2(dir) {
20183
+ const files = [];
20184
+ for (const entry of readdirSync5(dir)) {
20185
+ const full = join12(dir, entry);
20186
+ const stats = statSync4(full);
20187
+ if (stats.isDirectory())
20188
+ files.push(...walkFiles2(full));
20189
+ else
20190
+ files.push(full);
20191
+ }
20192
+ return files;
20193
+ }
20194
+ function resolveSnapshotPath(targetDir, snapshotPath) {
20195
+ const normalizedPath = normalize3(snapshotPath);
20196
+ if (normalizedPath.startsWith("..") || normalizedPath.includes(`${sep}..${sep}`) || normalizedPath.startsWith(sep)) {
20197
+ throw new Error(`Unsafe snapshot path: ${snapshotPath}`);
20198
+ }
20199
+ if (!toPosix(normalizedPath).startsWith(".skills/")) {
20200
+ throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
20201
+ }
20202
+ return join12(targetDir, normalizedPath);
20203
+ }
20204
+ function normalizeS3Prefix(prefix) {
20205
+ return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
20206
+ }
20207
+ function toPosix(path) {
20208
+ return path.split(/[\\/]+/).join("/");
20209
+ }
20210
+ function toIsoString(value) {
20211
+ const date5 = new Date(value);
20212
+ return Number.isNaN(date5.getTime()) ? value : date5.toISOString();
20213
+ }
20214
+ function sha256Hex(value) {
20215
+ return createHash2("sha256").update(value).digest("hex");
20216
+ }
20217
+ function normalizeHeaders(headers) {
20218
+ const result = {};
20219
+ for (const [key, value] of Object.entries(headers)) {
20220
+ result[key.toLowerCase()] = String(value).trim().replace(/\s+/g, " ");
20221
+ }
20222
+ return result;
20223
+ }
20224
+ function canonicalizeQuery(params) {
20225
+ return [...params.entries()].sort(([aKey, aValue], [bKey, bValue]) => aKey.localeCompare(bKey) || aValue.localeCompare(bValue)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
20226
+ }
20227
+ function encodeUriPath(pathname) {
20228
+ return pathname.split("/").map((segment) => encodeURIComponent(decodeURIComponent(segment)).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)).join("/");
20229
+ }
20230
+ function toAmzDate(date5) {
20231
+ return date5.toISOString().replace(/[:-]|\.\d{3}/g, "");
20232
+ }
20233
+ function getAwsSigningKey(secret, dateStamp, region, service) {
20234
+ const kDate = createHmac("sha256", `AWS4${secret}`).update(dateStamp).digest();
20235
+ const kRegion = createHmac("sha256", kDate).update(region).digest();
20236
+ const kService = createHmac("sha256", kRegion).update(service).digest();
20237
+ return createHmac("sha256", kService).update("aws4_request").digest();
20238
+ }
20239
+ function contentTypeForPath(path) {
20240
+ const lower = path.toLowerCase();
20241
+ if (lower.endsWith(".json"))
20242
+ return "application/json";
20243
+ if (lower.endsWith(".log") || lower.endsWith(".txt") || lower.endsWith(".ndjson"))
20244
+ return "text/plain";
20245
+ if (lower.endsWith(".md"))
20246
+ return "text/markdown";
20247
+ if (lower.endsWith(".png"))
20248
+ return "image/png";
20249
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))
20250
+ return "image/jpeg";
20251
+ if (lower.endsWith(".webp"))
20252
+ return "image/webp";
20253
+ if (lower.endsWith(".pdf"))
20254
+ return "application/pdf";
20255
+ return "application/octet-stream";
20256
+ }
20257
+ function toArrayBuffer(bytes) {
20258
+ const buffer = new ArrayBuffer(bytes.byteLength);
20259
+ new Uint8Array(buffer).set(bytes);
20260
+ return buffer;
20261
+ }
19669
20262
  export {
19670
20263
  writeRunLogs,
19671
20264
  writeRegistrySyncArtifact,
@@ -19675,11 +20268,14 @@ export {
19675
20268
  validatePortableSkillDirectory,
19676
20269
  validateCron,
19677
20270
  validateBlogArticleRunOptions,
20271
+ uploadSkillsSnapshotFilesToS3,
19678
20272
  updateSkillRun,
19679
20273
  unpinSkill,
19680
20274
  unpinProjectSkill,
19681
20275
  summarizeMcpToolContract,
20276
+ skillsPostgresSyncSchemaSql,
19682
20277
  skillExists,
20278
+ signSkillsAwsV4Request,
19683
20279
  setSkillDisabled,
19684
20280
  setScheduleEnabled,
19685
20281
  searchSkills,
@@ -19690,6 +20286,8 @@ export {
19690
20286
  sanitizePublicDiscoveryText,
19691
20287
  runSkill,
19692
20288
  runPortableSkill,
20289
+ resolveStorageConfig,
20290
+ resolveSkillsNativeStorageConfig,
19693
20291
  resolveSkillAlias,
19694
20292
  removeSkillForAgent,
19695
20293
  removeSkill,
@@ -19701,6 +20299,7 @@ export {
19701
20299
  publicDiscoveryDocumentation,
19702
20300
  publicDiscoveryDependencies,
19703
20301
  portPortableSkill,
20302
+ planSkillsS3SnapshotUpload,
19704
20303
  pinSkill,
19705
20304
  pinProjectSkill,
19706
20305
  parseSkillFrontmatter,
@@ -19729,6 +20328,16 @@ export {
19729
20328
  installSkillManifest,
19730
20329
  installSkillForAgent,
19731
20330
  installSkill,
20331
+ importSkillsLocalSnapshot,
20332
+ getStorageStatus,
20333
+ getStorageMode,
20334
+ getStorageDatabaseUrl,
20335
+ getStorageDatabaseEnv,
20336
+ getSkillsStorageStatus,
20337
+ getSkillsStorageMode,
20338
+ getSkillsStorageDatabaseUrl,
20339
+ getSkillsStorageDatabaseEnv,
20340
+ getSkillsNativeStorageStatus,
19732
20341
  getSkillsByTag,
19733
20342
  getSkillsByCategory,
19734
20343
  getSkillRunCostCents,
@@ -19770,10 +20379,14 @@ export {
19770
20379
  findSkillRun,
19771
20380
  findSimilarSkills,
19772
20381
  findPortableSkill,
20382
+ exportSkillsLocalSnapshot,
19773
20383
  ensureProjectConfig,
19774
20384
  enableSkill,
19775
20385
  disableSkill,
19776
20386
  describeMcpToolContracts,
20387
+ createSkillsSnapshotSyncRecord,
20388
+ createSkillsS3ObjectStore,
20389
+ createSkillsPostgresSyncStore,
19777
20390
  createSkillRun,
19778
20391
  createSkillMcpMetadata,
19779
20392
  createRemoteSkillsClient,
@@ -19782,11 +20395,20 @@ export {
19782
20395
  createLocalSkillManifest,
19783
20396
  completeSkillRun,
19784
20397
  clearRegistryCache,
20398
+ buildSkillsS3ObjectUrl,
19785
20399
  buildSkillsApiUrl,
19786
20400
  appendRunEvent,
19787
20401
  addSchedule,
20402
+ SkillsS3ObjectStore,
20403
+ SkillsPostgresSyncStore,
20404
+ STORAGE_TABLES,
19788
20405
  SKILL_ALIASES,
20406
+ SKILLS_STORAGE_TABLES,
20407
+ SKILLS_STORAGE_FALLBACK_ENV,
20408
+ SKILLS_STORAGE_ENV,
19789
20409
  SKILLS_PROJECT_DIR,
20410
+ SKILLS_NATIVE_STORAGE_FALLBACK_ENV,
20411
+ SKILLS_NATIVE_STORAGE_ENV,
19790
20412
  SKILLS_CLI_MCP_PARITY,
19791
20413
  SKILLS,
19792
20414
  RemoteSkillsClient,