@hasna/skills 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mcp.js CHANGED
@@ -3048,13 +3048,32 @@ var require_data = __commonJS((exports, module) => {
3048
3048
  };
3049
3049
  });
3050
3050
 
3051
- // ../../node_modules/.bun/fast-uri@3.1.5/node_modules/fast-uri/lib/utils.js
3051
+ // ../../node_modules/.bun/fast-uri@3.1.7/node_modules/fast-uri/lib/utils.js
3052
3052
  var require_utils = __commonJS((exports, module) => {
3053
3053
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3054
3054
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3055
+ var isPort = RegExp.prototype.test.bind(/^\d*$/u);
3055
3056
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3056
3057
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3057
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3058
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
3059
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
3060
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
3061
+ var BYTE_HEX = new Array(256);
3062
+ {
3063
+ const HEX_DIGITS = "0123456789ABCDEF";
3064
+ for (let i = 0;i < 256; i++) {
3065
+ BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
3066
+ }
3067
+ }
3068
+ function percentEncodeNonAscii(cp) {
3069
+ if (cp < 2048) {
3070
+ return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
3071
+ }
3072
+ if (cp < 65536) {
3073
+ return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3074
+ }
3075
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3076
+ }
3058
3077
  function stringArrayToHexStripped(input) {
3059
3078
  let acc = "";
3060
3079
  let code = 0;
@@ -3079,91 +3098,122 @@ var require_utils = __commonJS((exports, module) => {
3079
3098
  }
3080
3099
  return acc;
3081
3100
  }
3101
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
3102
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
3103
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
3082
3104
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
3083
- function consumeIsZone(buffer) {
3084
- buffer.length = 0;
3085
- return true;
3086
- }
3087
- function consumeHextets(buffer, address, output) {
3088
- if (buffer.length) {
3089
- const hex = stringArrayToHexStripped(buffer);
3090
- if (hex !== "") {
3091
- address.push(hex);
3092
- } else {
3093
- output.error = true;
3094
- return false;
3105
+ function isZoneIdentifier(zone) {
3106
+ if (zone.length === 0)
3107
+ return false;
3108
+ for (let i = 0;i < zone.length; i++) {
3109
+ if (isZoneCharacter(zone[i]))
3110
+ continue;
3111
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
3112
+ i += 2;
3113
+ continue;
3095
3114
  }
3096
- buffer.length = 0;
3115
+ return false;
3097
3116
  }
3098
3117
  return true;
3099
3118
  }
3100
- function getIPV6(input) {
3101
- let tokenCount = 0;
3102
- const output = { error: false, address: "", zone: "" };
3103
- const address = [];
3104
- const buffer = [];
3105
- let endipv6Encountered = false;
3106
- let endIpv6 = false;
3107
- let consume = consumeHextets;
3108
- for (let i = 0;i < input.length; i++) {
3109
- const cursor = input[i];
3110
- if (cursor === "[" || cursor === "]") {
3111
- continue;
3112
- }
3113
- if (cursor === ":") {
3114
- if (endipv6Encountered === true) {
3115
- endIpv6 = true;
3116
- }
3117
- if (!consume(buffer, address, output)) {
3118
- break;
3119
- }
3120
- if (++tokenCount > 7) {
3121
- output.error = true;
3122
- break;
3123
- }
3124
- if (i > 0 && input[i - 1] === ":") {
3125
- endipv6Encountered = true;
3126
- }
3127
- address.push(":");
3128
- continue;
3129
- } else if (cursor === "%") {
3130
- if (!consume(buffer, address, output)) {
3131
- break;
3119
+ function compressIPv6ZeroRun(hextets) {
3120
+ let bestStart = -1;
3121
+ let bestLength = 0;
3122
+ let runStart = -1;
3123
+ let runLength = 0;
3124
+ for (let i = 0;i < hextets.length; i++) {
3125
+ if (hextets[i] === "0") {
3126
+ if (runStart === -1)
3127
+ runStart = i;
3128
+ runLength++;
3129
+ if (runLength > bestLength) {
3130
+ bestLength = runLength;
3131
+ bestStart = runStart;
3132
3132
  }
3133
- consume = consumeIsZone;
3134
3133
  } else {
3135
- buffer.push(cursor);
3136
- continue;
3134
+ runStart = -1;
3135
+ runLength = 0;
3137
3136
  }
3138
3137
  }
3139
- if (buffer.length) {
3140
- if (consume === consumeIsZone) {
3141
- output.zone = buffer.join("");
3142
- } else if (endIpv6) {
3143
- address.push(buffer.join(""));
3144
- } else {
3145
- address.push(stringArrayToHexStripped(buffer));
3138
+ if (bestLength < 2)
3139
+ return hextets.join(":");
3140
+ const head = hextets.slice(0, bestStart).join(":");
3141
+ const tail = hextets.slice(bestStart + bestLength).join(":");
3142
+ return head + "::" + tail;
3143
+ }
3144
+ function normalizeIPv6Address(input) {
3145
+ const compression = input.indexOf("::");
3146
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1)
3147
+ return;
3148
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
3149
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
3150
+ if (compression !== -1) {
3151
+ if (left.length === 1 && left[0] === "")
3152
+ left.length = 0;
3153
+ if (right.length === 1 && right[0] === "")
3154
+ right.length = 0;
3155
+ }
3156
+ const parts = left.concat(right);
3157
+ let hextetCount = 0;
3158
+ for (let i = 0;i < parts.length; i++) {
3159
+ const part = parts[i];
3160
+ if (part === "")
3161
+ return;
3162
+ if (part.indexOf(".") !== -1) {
3163
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part))
3164
+ return;
3165
+ hextetCount += 2;
3166
+ continue;
3146
3167
  }
3168
+ if (!isHextet(part))
3169
+ return;
3170
+ parts[i] = parseInt(part, 16).toString(16);
3171
+ hextetCount++;
3147
3172
  }
3148
- output.address = address.join("");
3149
- return output;
3173
+ if (compression === -1) {
3174
+ if (hextetCount !== 8)
3175
+ return;
3176
+ return compressIPv6ZeroRun(parts);
3177
+ }
3178
+ if (hextetCount >= 8)
3179
+ return;
3180
+ const expanded = parts.slice(0, left.length);
3181
+ for (let i = hextetCount;i < 8; i++)
3182
+ expanded.push("0");
3183
+ for (let i = left.length;i < parts.length; i++)
3184
+ expanded.push(parts[i]);
3185
+ return compressIPv6ZeroRun(expanded);
3150
3186
  }
3151
3187
  function normalizeIPv6(host) {
3152
- if (findToken(host, ":") < 2) {
3153
- return { host, isIPV6: false };
3154
- }
3155
- const ipv62 = getIPV6(host);
3156
- if (!ipv62.error) {
3157
- let newHost = ipv62.address;
3158
- let escapedHost = ipv62.address;
3159
- if (ipv62.zone) {
3160
- newHost += "%" + ipv62.zone;
3161
- escapedHost += "%25" + ipv62.zone;
3162
- }
3163
- return { host: newHost, isIPV6: true, escapedHost };
3164
- } else {
3165
- return { host, isIPV6: false };
3166
- }
3188
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
3189
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
3190
+ if (hasBracket && !bracketed)
3191
+ return { host, isIPV6: false, error: true };
3192
+ let input = bracketed ? host.slice(1, -1) : host;
3193
+ if (bracketed && isIPvFuture(input)) {
3194
+ input = input.toLowerCase();
3195
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
3196
+ }
3197
+ if (findToken(input, ":") < 2) {
3198
+ return { host, isIPV6: false, error: bracketed };
3199
+ }
3200
+ let zoneIdentifier = "";
3201
+ const zoneSeparator = input.indexOf("%");
3202
+ if (zoneSeparator !== -1) {
3203
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
3204
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
3205
+ if (!isZoneIdentifier(zoneIdentifier))
3206
+ return { host, isIPV6: false, error: true };
3207
+ input = input.slice(0, zoneSeparator);
3208
+ }
3209
+ const address = normalizeIPv6Address(input);
3210
+ if (address === undefined)
3211
+ return { host, isIPV6: false, error: true };
3212
+ return {
3213
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
3214
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
3215
+ isIPV6: true
3216
+ };
3167
3217
  }
3168
3218
  function findToken(str, token) {
3169
3219
  let ind = 0;
@@ -3283,7 +3333,8 @@ var require_utils = __commonJS((exports, module) => {
3283
3333
  function normalizePathEncoding(input) {
3284
3334
  let output = "";
3285
3335
  for (let i = 0;i < input.length; i++) {
3286
- if (input[i] === "%" && i + 2 < input.length) {
3336
+ const ch = input[i];
3337
+ if (ch === "%" && i + 2 < input.length) {
3287
3338
  const hex = input.slice(i + 1, i + 3);
3288
3339
  if (isHexPair(hex)) {
3289
3340
  const normalizedHex = hex.toUpperCase();
@@ -3297,10 +3348,152 @@ var require_utils = __commonJS((exports, module) => {
3297
3348
  continue;
3298
3349
  }
3299
3350
  }
3300
- if (isPathCharacter(input[i])) {
3301
- output += input[i];
3351
+ if (isPathCharacter(ch)) {
3352
+ output += ch;
3353
+ } else {
3354
+ const code = input.charCodeAt(i);
3355
+ if (code < 128) {
3356
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3357
+ } else if (code < 55296 || code > 57343) {
3358
+ output += percentEncodeNonAscii(code);
3359
+ } else if (code <= 56319 && i + 1 < input.length) {
3360
+ const low = input.charCodeAt(i + 1);
3361
+ if (low >= 56320 && low <= 57343) {
3362
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3363
+ i++;
3364
+ } else {
3365
+ output += percentEncodeNonAscii(65533);
3366
+ }
3367
+ } else {
3368
+ output += percentEncodeNonAscii(65533);
3369
+ }
3370
+ }
3371
+ }
3372
+ return output;
3373
+ }
3374
+ function serializePathEncoding(input, pathNoScheme = false) {
3375
+ let output = "";
3376
+ let firstSegment = pathNoScheme && input[0] !== "/";
3377
+ for (let i = 0;i < input.length; i++) {
3378
+ const ch = input[i];
3379
+ if (ch === "%" && i + 2 < input.length) {
3380
+ const hex = input.slice(i + 1, i + 3);
3381
+ if (isHexPair(hex)) {
3382
+ output += "%" + hex.toUpperCase();
3383
+ i += 2;
3384
+ continue;
3385
+ }
3386
+ }
3387
+ if (ch === "/") {
3388
+ firstSegment = false;
3389
+ }
3390
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
3391
+ output += ch;
3392
+ } else {
3393
+ const code = input.charCodeAt(i);
3394
+ if (code < 128) {
3395
+ output += BYTE_HEX[code];
3396
+ } else if (code < 55296 || code > 57343) {
3397
+ output += percentEncodeNonAscii(code);
3398
+ } else if (code <= 56319 && i + 1 < input.length) {
3399
+ const low = input.charCodeAt(i + 1);
3400
+ if (low >= 56320 && low <= 57343) {
3401
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3402
+ i++;
3403
+ } else {
3404
+ output += percentEncodeNonAscii(65533);
3405
+ }
3406
+ } else {
3407
+ output += percentEncodeNonAscii(65533);
3408
+ }
3409
+ }
3410
+ }
3411
+ return output;
3412
+ }
3413
+ function encodeComponent(input, isAllowed) {
3414
+ let output = "";
3415
+ for (let i = 0;i < input.length; i++) {
3416
+ const ch = input[i];
3417
+ if (ch === "%" && i + 2 < input.length) {
3418
+ const hex = input.slice(i + 1, i + 3);
3419
+ if (isHexPair(hex)) {
3420
+ output += "%" + hex.toUpperCase();
3421
+ i += 2;
3422
+ continue;
3423
+ }
3424
+ }
3425
+ if (isAllowed(ch)) {
3426
+ output += ch;
3427
+ } else {
3428
+ const code = input.charCodeAt(i);
3429
+ if (code < 128) {
3430
+ output += BYTE_HEX[code];
3431
+ } else if (code < 55296 || code > 57343) {
3432
+ output += percentEncodeNonAscii(code);
3433
+ } else if (code <= 56319 && i + 1 < input.length) {
3434
+ const low = input.charCodeAt(i + 1);
3435
+ if (low >= 56320 && low <= 57343) {
3436
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3437
+ i++;
3438
+ } else {
3439
+ output += percentEncodeNonAscii(65533);
3440
+ }
3441
+ } else {
3442
+ output += percentEncodeNonAscii(65533);
3443
+ }
3444
+ }
3445
+ }
3446
+ return output;
3447
+ }
3448
+ function encodeUserinfo(input) {
3449
+ return encodeComponent(input, isUserinfoCharacter);
3450
+ }
3451
+ function encodeQuery(input) {
3452
+ return encodeComponent(input, isQueryFragmentCharacter);
3453
+ }
3454
+ function encodeFragment(input) {
3455
+ return encodeComponent(input, isQueryFragmentCharacter);
3456
+ }
3457
+ function isEscapeSafe(cp) {
3458
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
3459
+ }
3460
+ function normalizeQueryFragmentEncoding(input) {
3461
+ let output = "";
3462
+ for (let i = 0;i < input.length; i++) {
3463
+ const ch = input[i];
3464
+ if (ch === "%" && i + 2 < input.length) {
3465
+ const hex = input.slice(i + 1, i + 3);
3466
+ if (isHexPair(hex)) {
3467
+ const normalizedHex = hex.toUpperCase();
3468
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3469
+ if (isUnreserved(decoded)) {
3470
+ output += decoded;
3471
+ } else {
3472
+ output += "%" + normalizedHex;
3473
+ }
3474
+ i += 2;
3475
+ continue;
3476
+ }
3477
+ }
3478
+ if (isQueryFragmentCharacter(ch)) {
3479
+ output += ch;
3302
3480
  } else {
3303
- output += escape(input[i]);
3481
+ const code = input.charCodeAt(i);
3482
+ if (code < 128) {
3483
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3484
+ } else if (code < 55296 || code > 57343) {
3485
+ output += percentEncodeNonAscii(code);
3486
+ } else if (code <= 56319 && i + 1 < input.length) {
3487
+ const low = input.charCodeAt(i + 1);
3488
+ if (low >= 56320 && low <= 57343) {
3489
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3490
+ i++;
3491
+ } else {
3492
+ output += percentEncodeNonAscii(65533);
3493
+ }
3494
+ } else {
3495
+ output += percentEncodeNonAscii(65533);
3496
+ }
3304
3497
  }
3305
3498
  }
3306
3499
  return output;
@@ -3323,14 +3516,18 @@ var require_utils = __commonJS((exports, module) => {
3323
3516
  function recomposeAuthority(component) {
3324
3517
  const uriTokens = [];
3325
3518
  if (component.userinfo !== undefined) {
3326
- uriTokens.push(component.userinfo);
3519
+ uriTokens.push(encodeUserinfo(component.userinfo));
3327
3520
  uriTokens.push("@");
3328
3521
  }
3329
3522
  if (component.host !== undefined) {
3330
- let host = unescape(component.host);
3523
+ let host = component.host;
3331
3524
  if (!isIPv4(host)) {
3332
- const ipV6res = normalizeIPv6(host);
3333
- if (ipV6res.isIPV6 === true) {
3525
+ let ipV6res = normalizeIPv6(host);
3526
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3527
+ host = normalizePercentEncoding(host, true);
3528
+ ipV6res = normalizeIPv6(host);
3529
+ }
3530
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3334
3531
  host = `[${ipV6res.escapedHost}]`;
3335
3532
  } else {
3336
3533
  host = reescapeHostDelimiters(host, false);
@@ -3339,8 +3536,12 @@ var require_utils = __commonJS((exports, module) => {
3339
3536
  uriTokens.push(host);
3340
3537
  }
3341
3538
  if (typeof component.port === "number" || typeof component.port === "string") {
3539
+ const port = String(component.port);
3540
+ if (!isPort(port)) {
3541
+ throw new TypeError("URI port is malformed.");
3542
+ }
3342
3543
  uriTokens.push(":");
3343
- uriTokens.push(String(component.port));
3544
+ uriTokens.push(port);
3344
3545
  }
3345
3546
  return uriTokens.length ? uriTokens.join("") : undefined;
3346
3547
  }
@@ -3350,6 +3551,11 @@ var require_utils = __commonJS((exports, module) => {
3350
3551
  reescapeHostDelimiters,
3351
3552
  normalizePercentEncoding,
3352
3553
  normalizePathEncoding,
3554
+ serializePathEncoding,
3555
+ normalizeQueryFragmentEncoding,
3556
+ encodeUserinfo,
3557
+ encodeQuery,
3558
+ encodeFragment,
3353
3559
  escapePreservingEscapes,
3354
3560
  removeDotSegments,
3355
3561
  isIPv4,
@@ -3359,10 +3565,10 @@ var require_utils = __commonJS((exports, module) => {
3359
3565
  };
3360
3566
  });
3361
3567
 
3362
- // ../../node_modules/.bun/fast-uri@3.1.5/node_modules/fast-uri/lib/schemes.js
3568
+ // ../../node_modules/.bun/fast-uri@3.1.7/node_modules/fast-uri/lib/schemes.js
3363
3569
  var require_schemes = __commonJS((exports, module) => {
3364
3570
  var { isUUID } = require_utils();
3365
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
3571
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
3366
3572
  var supportedSchemeNames = [
3367
3573
  "http",
3368
3574
  "https",
@@ -3417,9 +3623,10 @@ var require_schemes = __commonJS((exports, module) => {
3417
3623
  wsComponent.secure = undefined;
3418
3624
  }
3419
3625
  if (wsComponent.resourceName) {
3420
- const [path, query] = wsComponent.resourceName.split("?");
3626
+ const queryIndex = wsComponent.resourceName.indexOf("?");
3627
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3421
3628
  wsComponent.path = path && path !== "/" ? path : undefined;
3422
- wsComponent.query = query;
3629
+ wsComponent.query = queryIndex === -1 ? undefined : wsComponent.resourceName.slice(queryIndex + 1);
3423
3630
  wsComponent.resourceName = undefined;
3424
3631
  }
3425
3632
  wsComponent.fragment = undefined;
@@ -3431,7 +3638,7 @@ var require_schemes = __commonJS((exports, module) => {
3431
3638
  return urnComponent;
3432
3639
  }
3433
3640
  const matches = urnComponent.path.match(URN_REG);
3434
- if (matches) {
3641
+ if (matches && matches[0] === urnComponent.path) {
3435
3642
  const scheme = options.scheme || urnComponent.scheme || "urn";
3436
3643
  urnComponent.nid = matches[1].toLowerCase();
3437
3644
  urnComponent.nss = matches[2];
@@ -3533,10 +3740,19 @@ var require_schemes = __commonJS((exports, module) => {
3533
3740
  };
3534
3741
  });
3535
3742
 
3536
- // ../../node_modules/.bun/fast-uri@3.1.5/node_modules/fast-uri/index.js
3743
+ // ../../node_modules/.bun/fast-uri@3.1.7/node_modules/fast-uri/index.js
3537
3744
  var require_fast_uri = __commonJS((exports, module) => {
3538
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3745
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3539
3746
  var { SCHEMES, getSchemeHandler } = require_schemes();
3747
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
3748
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
3749
+ function decodeValidScheme(scheme) {
3750
+ const decodedScheme = unescape(String(scheme));
3751
+ if (!VALID_SCHEME.test(decodedScheme)) {
3752
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
3753
+ }
3754
+ return decodedScheme;
3755
+ }
3540
3756
  function normalize(uri, options) {
3541
3757
  if (typeof uri === "string") {
3542
3758
  uri = normalizeString(uri, options);
@@ -3547,12 +3763,34 @@ var require_fast_uri = __commonJS((exports, module) => {
3547
3763
  }
3548
3764
  function resolve(baseURI, relativeURI, options) {
3549
3765
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3550
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3551
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3552
- if (baseMalformed || relativeMalformed) {
3766
+ const {
3767
+ parsed: baseParsed,
3768
+ malformedAuthorityOrPort: baseMalformed,
3769
+ malformedPercentEncoding: baseMalformedPercentEncoding,
3770
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
3771
+ malformedHost: baseMalformedHost,
3772
+ malformedScheme: baseMalformedScheme
3773
+ } = parseWithStatus(baseURI, schemelessOptions);
3774
+ const {
3775
+ parsed: relativeParsed,
3776
+ malformedAuthorityOrPort: relativeMalformed,
3777
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
3778
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
3779
+ malformedHost: relativeMalformedHost,
3780
+ malformedScheme: relativeMalformedScheme
3781
+ } = parseWithStatus(relativeURI, schemelessOptions);
3782
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
3553
3783
  throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3554
3784
  }
3555
3785
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
3786
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
3787
+ const resolvedHost = resolved.host;
3788
+ const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
3789
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
3790
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
3791
+ if (resolved.error && !encodedASCIIHost) {
3792
+ throw new Error(resolved.error);
3793
+ }
3556
3794
  schemelessOptions.skipEscape = true;
3557
3795
  return serialize(resolved, schemelessOptions);
3558
3796
  }
@@ -3612,7 +3850,7 @@ var require_fast_uri = __commonJS((exports, module) => {
3612
3850
  function equal(uriA, uriB, options) {
3613
3851
  const normalizedA = normalizeComparableURI(uriA, options);
3614
3852
  const normalizedB = normalizeComparableURI(uriB, options);
3615
- return normalizedA !== undefined && normalizedB !== undefined && normalizedA.toLowerCase() === normalizedB.toLowerCase();
3853
+ return normalizedA !== undefined && normalizedB !== undefined && normalizedA === normalizedB;
3616
3854
  }
3617
3855
  function serialize(cmpts, opts) {
3618
3856
  const component = {
@@ -3633,20 +3871,23 @@ var require_fast_uri = __commonJS((exports, module) => {
3633
3871
  };
3634
3872
  const options = Object.assign({}, opts);
3635
3873
  const uriTokens = [];
3874
+ if (component.scheme) {
3875
+ component.scheme = decodeValidScheme(component.scheme);
3876
+ }
3636
3877
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3637
3878
  if (schemeHandler && schemeHandler.serialize)
3638
3879
  schemeHandler.serialize(component, options);
3880
+ const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined;
3881
+ const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority;
3639
3882
  if (component.path !== undefined) {
3640
3883
  if (!options.skipEscape) {
3641
- component.path = escapePreservingEscapes(component.path);
3642
- if (component.scheme !== undefined) {
3643
- component.path = component.path.split("%3A").join(":");
3644
- }
3884
+ component.path = serializePathEncoding(component.path, pathNoScheme);
3645
3885
  } else {
3646
3886
  component.path = normalizePercentEncoding(component.path);
3647
3887
  }
3648
3888
  }
3649
3889
  if (options.reference !== "suffix" && component.scheme) {
3890
+ component.scheme = decodeValidScheme(component.scheme);
3650
3891
  uriTokens.push(component.scheme, ":");
3651
3892
  }
3652
3893
  const authority = recomposeAuthority(component);
@@ -3664,16 +3905,19 @@ var require_fast_uri = __commonJS((exports, module) => {
3664
3905
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3665
3906
  s = removeDotSegments(s);
3666
3907
  }
3908
+ if (pathNoScheme) {
3909
+ s = serializePathEncoding(s, true);
3910
+ }
3667
3911
  if (authority === undefined && s[0] === "/" && s[1] === "/") {
3668
3912
  s = "/%2F" + s.slice(2);
3669
3913
  }
3670
3914
  uriTokens.push(s);
3671
3915
  }
3672
3916
  if (component.query !== undefined) {
3673
- uriTokens.push("?", component.query);
3917
+ uriTokens.push("?", encodeQuery(component.query));
3674
3918
  }
3675
3919
  if (component.fragment !== undefined) {
3676
- uriTokens.push("#", component.fragment);
3920
+ uriTokens.push("#", encodeFragment(component.fragment));
3677
3921
  }
3678
3922
  return uriTokens.join("");
3679
3923
  }
@@ -3689,6 +3933,36 @@ var require_fast_uri = __commonJS((exports, module) => {
3689
3933
  }
3690
3934
  return;
3691
3935
  }
3936
+ function hasMalformedPercentEncoding(component) {
3937
+ if (component === undefined)
3938
+ return false;
3939
+ let percent = component.indexOf("%");
3940
+ while (percent !== -1) {
3941
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
3942
+ return true;
3943
+ }
3944
+ percent = component.indexOf("%", percent + 3);
3945
+ }
3946
+ return false;
3947
+ }
3948
+ function isIPLiteral(host) {
3949
+ return host[0] === "[" && host[host.length - 1] === "]";
3950
+ }
3951
+ function hasMalformedComponentPercentEncoding(matches) {
3952
+ const host = matches[4];
3953
+ return hasMalformedPercentEncoding(matches[3]) || host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
3954
+ }
3955
+ function canonicalizeHost(parsed, options, schemeHandler, isIP) {
3956
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3957
+ try {
3958
+ parsed.host = new URL("http://" + parsed.host).hostname;
3959
+ } catch (e) {
3960
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3961
+ return true;
3962
+ }
3963
+ }
3964
+ return false;
3965
+ }
3692
3966
  function parseWithStatus(uri, opts) {
3693
3967
  const options = Object.assign({}, opts);
3694
3968
  const parsed = {
@@ -3701,6 +3975,11 @@ var require_fast_uri = __commonJS((exports, module) => {
3701
3975
  fragment: undefined
3702
3976
  };
3703
3977
  let malformedAuthorityOrPort = false;
3978
+ let malformedPercentEncoding = false;
3979
+ let malformedSchemeSpecific = false;
3980
+ let malformedHost = false;
3981
+ let malformedIPLiteral = false;
3982
+ let malformedScheme = false;
3704
3983
  let isIP = false;
3705
3984
  if (options.reference === "suffix") {
3706
3985
  if (options.scheme) {
@@ -3737,6 +4016,19 @@ var require_fast_uri = __commonJS((exports, module) => {
3737
4016
  parsed.path = matches[6] || "";
3738
4017
  parsed.query = matches[7];
3739
4018
  parsed.fragment = matches[8];
4019
+ if (parsed.scheme !== undefined) {
4020
+ const decodedScheme = unescape(parsed.scheme);
4021
+ if (VALID_SCHEME.test(decodedScheme)) {
4022
+ parsed.scheme = decodedScheme.toLowerCase();
4023
+ } else {
4024
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4025
+ malformedScheme = true;
4026
+ }
4027
+ }
4028
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
4029
+ if (malformedPercentEncoding) {
4030
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
4031
+ }
3740
4032
  if (isNaN(parsed.port)) {
3741
4033
  parsed.port = matches[5];
3742
4034
  }
@@ -3748,9 +4040,16 @@ var require_fast_uri = __commonJS((exports, module) => {
3748
4040
  if (parsed.host) {
3749
4041
  const ipv4result = isIPv4(parsed.host);
3750
4042
  if (ipv4result === false) {
4043
+ const bracketedIPLiteral = isIPLiteral(parsed.host);
4044
+ const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
3751
4045
  const ipv6result = normalizeIPv6(parsed.host);
3752
- parsed.host = ipv6result.host.toLowerCase();
3753
- isIP = ipv6result.isIPV6;
4046
+ isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4047
+ malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4048
+ parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4049
+ if (malformedIPLiteral) {
4050
+ parsed.error = parsed.error || "URI host is malformed.";
4051
+ malformedAuthorityOrPort = true;
4052
+ }
3754
4053
  } else {
3755
4054
  isIP = true;
3756
4055
  }
@@ -3768,42 +4067,36 @@ var require_fast_uri = __commonJS((exports, module) => {
3768
4067
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
3769
4068
  }
3770
4069
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
3771
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3772
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3773
- try {
3774
- parsed.host = new URL("http://" + parsed.host).hostname;
3775
- } catch (e) {
3776
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3777
- }
3778
- }
4070
+ if (!malformedIPLiteral) {
4071
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
3779
4072
  }
3780
4073
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
3781
4074
  if (uri.indexOf("%") !== -1) {
3782
- if (parsed.scheme !== undefined) {
3783
- parsed.scheme = unescape(parsed.scheme);
3784
- }
3785
- if (parsed.host !== undefined) {
3786
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
4075
+ if (parsed.host !== undefined && !malformedIPLiteral) {
4076
+ const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4077
+ parsed.host = reescapeHostDelimiters(host, isIP);
3787
4078
  }
3788
4079
  }
3789
4080
  if (parsed.path) {
3790
4081
  parsed.path = normalizePathEncoding(parsed.path);
3791
4082
  }
4083
+ if (parsed.query) {
4084
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query);
4085
+ }
3792
4086
  if (parsed.fragment) {
3793
- try {
3794
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3795
- } catch {
3796
- parsed.error = parsed.error || "URI malformed";
3797
- }
4087
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
3798
4088
  }
3799
4089
  }
3800
4090
  if (schemeHandler && schemeHandler.parse) {
3801
4091
  schemeHandler.parse(parsed, options);
4092
+ if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
4093
+ malformedSchemeSpecific = true;
4094
+ }
3802
4095
  }
3803
4096
  } else {
3804
4097
  parsed.error = parsed.error || "URI can not be parsed.";
3805
4098
  }
3806
- return { parsed, malformedAuthorityOrPort };
4099
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
3807
4100
  }
3808
4101
  function parse6(uri, opts) {
3809
4102
  return parseWithStatus(uri, opts).parsed;
@@ -3812,20 +4105,28 @@ var require_fast_uri = __commonJS((exports, module) => {
3812
4105
  return normalizeStringWithStatus(uri, opts).normalized;
3813
4106
  }
3814
4107
  function normalizeStringWithStatus(uri, opts) {
3815
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
4108
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
3816
4109
  return {
3817
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3818
- malformedAuthorityOrPort
4110
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
4111
+ malformedAuthorityOrPort,
4112
+ malformedPercentEncoding,
4113
+ malformedSchemeSpecific,
4114
+ malformedHost,
4115
+ malformedScheme
3819
4116
  };
3820
4117
  }
3821
4118
  function normalizeComparableURI(uri, opts) {
3822
- if (typeof uri === "string") {
3823
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3824
- return malformedAuthorityOrPort ? undefined : normalized;
4119
+ if (typeof uri !== "string" && typeof uri !== "object") {
4120
+ return;
3825
4121
  }
3826
- if (typeof uri === "object") {
3827
- return serialize(uri, opts);
4122
+ let value;
4123
+ try {
4124
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
4125
+ } catch {
4126
+ return;
3828
4127
  }
4128
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
4129
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized;
3829
4130
  }
3830
4131
  var fastUri = {
3831
4132
  SCHEMES,
@@ -6631,451 +6932,855 @@ var require_dist = __commonJS((exports, module) => {
6631
6932
  exports.default = formatsPlugin;
6632
6933
  });
6633
6934
 
6634
- // src/lib/retired-settings.ts
6635
- function isRetiredModeEnvVar(name, app) {
6636
- const upper = name.toUpperCase();
6637
- if (!upper.includes(app.toUpperCase()))
6638
- return false;
6639
- return RETIRED_ENV_SUFFIXES.some((suffix) => upper.endsWith(suffix));
6935
+ // src/lib/remote-run-contract.ts
6936
+ function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
6937
+ const record3 = isRecord3(payload) ? payload : {};
6938
+ return {
6939
+ contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
6940
+ ...pickString(record3, "id"),
6941
+ skill: pickStringValue(record3, "skill") ?? fallbackSkill,
6942
+ ...pickString(record3, "requestedSlug"),
6943
+ ...pickString(record3, "status"),
6944
+ ...pickNumber(record3, "exitCode"),
6945
+ ...pickString(record3, "correlationId"),
6946
+ ...pickString(record3, "createdAt"),
6947
+ ...pickString(record3, "startedAt"),
6948
+ ...pickString(record3, "completedAt"),
6949
+ ...pickNumber(record3, "durationMs"),
6950
+ ...pickString(record3, "outputType"),
6951
+ ...hasOwn(record3, "outputPreview") ? { outputPreview: record3.outputPreview } : {},
6952
+ ...pickString(record3, "errorCode"),
6953
+ ...pickString(record3, "errorMessage"),
6954
+ ...pickString(record3, "error"),
6955
+ ...pickString(record3, "code"),
6956
+ ...hasOwn(record3, "details") ? { details: record3.details } : {}
6957
+ };
6640
6958
  }
6641
- function findRetiredModeEnvVars(env, app) {
6642
- const found = [];
6643
- for (const [name, value] of Object.entries(env)) {
6644
- if (value === undefined || value === "")
6645
- continue;
6646
- if (isRetiredModeEnvVar(name, app))
6647
- found.push({ name, value });
6648
- }
6649
- return found.sort((a, b) => a.name.localeCompare(b.name));
6959
+ function isRecord3(value) {
6960
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
6650
6961
  }
6651
- function assertNoRetiredModeEnvVars(env, options) {
6652
- const found = findRetiredModeEnvVars(env, options.app);
6653
- if (found.length === 0)
6654
- return;
6655
- const names = found.map((entry) => entry.name);
6656
- throw new RetiredSettingError(names[0], `${names.join(", ")} ${names.length === 1 ? "is" : "are"} no longer read. ` + "Deployment modes were removed: where a server keeps its data is decided by the " + `database it is given, not by a declared label. Set ${options.replacement} to a ` + "postgres:// URL to use PostgreSQL, or leave it unset for the on-box SQLite database. " + `Then unset ${names.join(" and ")}. ` + "Refused rather than ignored, because a discarded setting looks exactly like a " + "working one until something needs the data.");
6962
+ function hasOwn(record3, key) {
6963
+ return Object.prototype.hasOwnProperty.call(record3, key);
6657
6964
  }
6658
- function assertNoRetiredConfigKeys(config2, source) {
6659
- for (const [key, replacement] of Object.entries(RETIRED_CONFIG_KEYS)) {
6660
- if (!(key in config2))
6661
- continue;
6662
- throw new RetiredSettingError(key, `${source}: "${key}" is no longer a configuration key. ` + "Deployment modes were removed: a Skills client either has an API origin " + "configured or it does not, and that is the whole of it. " + `Use "${replacement}" instead (skills config set ${replacement} <origin>), and ` + `remove the old key with: skills config unset ${key}. ` + "Refused rather than ignored, because silently dropping it would leave an " + "operator believing they had pointed this install at a server.");
6663
- }
6965
+ function pickString(record3, key) {
6966
+ const value = pickStringValue(record3, key);
6967
+ return value === undefined ? {} : { [key]: value };
6664
6968
  }
6665
- var RETIRED_ENV_SUFFIXES, RETIRED_CONFIG_KEYS, RetiredSettingError;
6666
- var init_retired_settings = __esm(() => {
6667
- RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
6668
- RETIRED_CONFIG_KEYS = {
6669
- mode: "apiUrl"
6670
- };
6671
- RetiredSettingError = class RetiredSettingError extends Error {
6672
- code = "RETIRED_SETTING";
6673
- setting;
6674
- constructor(setting, message) {
6675
- super(message);
6676
- this.name = "RetiredSettingError";
6677
- this.setting = setting;
6678
- }
6679
- };
6680
- });
6969
+ function pickStringValue(record3, key) {
6970
+ const value = record3[key];
6971
+ return typeof value === "string" ? value : undefined;
6972
+ }
6973
+ function pickNumber(record3, key) {
6974
+ const value = record3[key];
6975
+ return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
6976
+ }
6977
+ var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
6681
6978
 
6682
- // src/lib/app-home.ts
6683
- import { existsSync } from "fs";
6684
- import { homedir } from "os";
6685
- import { join, resolve } from "path";
6686
- import { homedir as pathsResolverHomedir } from "os";
6687
- import { join as pathsResolverJoin } from "path";
6688
- function pathsResolverAssertApp(app) {
6689
- if (typeof app !== "string" || app.length === 0) {
6690
- throw new TypeError("paths: app must be a non-empty string");
6691
- }
6692
- if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
6693
- throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
6694
- }
6979
+ // ../contracts/dist/client/transport.js
6980
+ import { isIP } from "net";
6981
+ import { spawnSync } from "child_process";
6982
+ import { closeSync, fstatSync, openSync, readFileSync as readFileSync11 } from "fs";
6983
+ import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
6984
+ import { createRequire } from "module";
6985
+ import { hostname as osHostname } from "os";
6986
+ import { isAbsolute as isAbsolute3, join as join13 } from "path";
6987
+ function envToken(name) {
6988
+ return name.toUpperCase().replace(/-/g, "_");
6989
+ }
6990
+ function clientTransportEnvKeys(name) {
6991
+ const envSegment = envToken(name);
6992
+ return {
6993
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
6994
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
6995
+ };
6695
6996
  }
6696
- function pathsResolverAssertKind(kind) {
6697
- if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
6698
- throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
6699
- }
6997
+ function credentialOverrideEnvKey(name) {
6998
+ return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
6700
6999
  }
6701
- function pathsResolverBaseDir(kind, options) {
6702
- pathsResolverAssertKind(kind);
6703
- const env = options.env ?? process.env;
6704
- const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
6705
- if (typeof override === "string" && override.length > 0)
7000
+ function credentialPointerEnvKey(name) {
7001
+ return `HASNA_${envToken(name)}_API_KEY_REF`;
7002
+ }
7003
+ function homeDir(env) {
7004
+ const home = env.HOME?.trim();
7005
+ return home ? home : null;
7006
+ }
7007
+ function absoluteOverride(env, key) {
7008
+ const value = env[key]?.trim();
7009
+ return value && isAbsolute3(value) ? value : null;
7010
+ }
7011
+ function hasnaHomeDir(env) {
7012
+ const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
7013
+ if (override)
6706
7014
  return override;
6707
- const home = options.home ?? pathsResolverHomedir();
6708
- const platform = options.platform ?? process.platform;
6709
- if (platform === "darwin") {
6710
- switch (kind) {
6711
- case "config":
6712
- case "data":
6713
- return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
6714
- case "cache":
6715
- return pathsResolverJoin(home, "Library", "Caches", "Hasna");
6716
- case "state":
6717
- return pathsResolverJoin(home, "Library", "Logs", "Hasna");
6718
- }
6719
- }
6720
- switch (kind) {
6721
- case "config":
6722
- return pathsResolverJoin(home, ".config", "hasna");
6723
- case "data":
6724
- return pathsResolverJoin(home, ".local", "share", "hasna");
6725
- case "state":
6726
- return pathsResolverJoin(home, ".local", "state", "hasna");
6727
- case "cache":
6728
- return pathsResolverJoin(home, ".cache", "hasna");
6729
- }
7015
+ const home = homeDir(env);
7016
+ return home ? join13(home, HASNA_HOME_DIR) : null;
7017
+ }
7018
+ function appConfigDir(name, env) {
7019
+ const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
7020
+ if (configRoot)
7021
+ return join13(configRoot, name);
7022
+ const root = hasnaHomeDir(env);
7023
+ return root ? join13(root, name, CONFIG_SUBDIR) : null;
7024
+ }
7025
+ function credentialDiskSourceList(name, env, profile = null) {
7026
+ if (!SAFE_APP_SLUG.test(name))
7027
+ return [];
7028
+ const directory = appConfigDir(name, env);
7029
+ if (!directory)
7030
+ return [];
7031
+ const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
7032
+ return [{ path: join13(directory, file), tier: "disk" }];
6730
7033
  }
6731
- function pathsResolverResolve(kind, options) {
6732
- pathsResolverAssertApp(options.app);
6733
- const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
6734
- return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
7034
+ function credentialDiskSources(name, env) {
7035
+ return credentialDiskSourceList(name, env, null).map((s) => s.path);
6735
7036
  }
6736
- function dataDir(options) {
6737
- return pathsResolverResolve("data", options);
7037
+ function profileDiskSources(name, env, profile) {
7038
+ return credentialDiskSourceList(name, env, profile).map((s) => s.path);
6738
7039
  }
6739
- function effectiveHome() {
6740
- return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
7040
+ function parseEnvFile(text) {
7041
+ const values = new Map;
7042
+ const unusable = new Set;
7043
+ for (const rawLine of text.split(/\r?\n/)) {
7044
+ const line = rawLine.trim();
7045
+ if (line.length === 0 || line.startsWith("#"))
7046
+ continue;
7047
+ const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
7048
+ const equals = withoutExport.indexOf("=");
7049
+ if (equals <= 0)
7050
+ continue;
7051
+ const key = withoutExport.slice(0, equals).trim();
7052
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
7053
+ continue;
7054
+ let value = withoutExport.slice(equals + 1).trim();
7055
+ const quote = value[0];
7056
+ if (quote === '"' || quote === "'") {
7057
+ if (value.length < 2 || !value.endsWith(quote)) {
7058
+ unusable.add(key);
7059
+ continue;
7060
+ }
7061
+ value = value.slice(1, -1);
7062
+ }
7063
+ if (value.trim().length === 0) {
7064
+ unusable.add(key);
7065
+ continue;
7066
+ }
7067
+ if (values.has(key) && values.get(key) !== value)
7068
+ unusable.add(key);
7069
+ values.set(key, value);
7070
+ }
7071
+ return { values, unusable };
6741
7072
  }
6742
- function legacyDataRoot() {
6743
- return join(effectiveHome(), ".hasna", "skills");
7073
+ function configFileModeAllowed(mode) {
7074
+ const permissions = mode & 4095;
7075
+ return permissions === 256 || permissions === 384;
6744
7076
  }
6745
- function resolverDataRoot(home = effectiveHome(), env) {
6746
- return dataDir({ app: "skills", home, env });
7077
+ function configFileReadsCoherent(before, after) {
7078
+ return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs;
6747
7079
  }
6748
- function adoptResolverDataRoot(resolved, env = process.env) {
6749
- const dataOverride = env.HASNA_DATA_HOME;
6750
- if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
6751
- return true;
6752
- return existsSync(join(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join(resolved, GLOBAL_CONFIG_FILENAME));
7080
+ function readAppConfigFile(path) {
7081
+ const unsafe = (reason) => {
7082
+ throw new CredentialFileUnsafeError(path, reason);
7083
+ };
7084
+ let fd = -1;
7085
+ try {
7086
+ fd = openSync(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
7087
+ } catch (error2) {
7088
+ const code = error2.code;
7089
+ if (code === "ENOENT" || code === "ENOTDIR")
7090
+ return null;
7091
+ if (code === "ELOOP")
7092
+ unsafe("the path is a symlink");
7093
+ unsafe(`the path could not be opened (${code ?? "unknown error"})`);
7094
+ }
7095
+ try {
7096
+ const before = fstatSync(fd);
7097
+ if (!before.isFile())
7098
+ unsafe("the path is not a regular file");
7099
+ if (!configFileModeAllowed(before.mode)) {
7100
+ unsafe(`permission mode ${(before.mode & 4095).toString(8).padStart(4, "0")} is not owner-only 0400 or 0600`);
7101
+ }
7102
+ const uid = process.getuid?.() ?? process.geteuid?.();
7103
+ if (uid !== undefined && before.uid !== uid)
7104
+ unsafe("the file is not owned by the current user");
7105
+ if (before.size > MAX_CREDENTIAL_FILE_BYTES)
7106
+ unsafe("the file exceeds the size limit");
7107
+ const bytes = readFileSync11(fd);
7108
+ const after = fstatSync(fd);
7109
+ if (!configFileReadsCoherent(before, after)) {
7110
+ unsafe("the file changed while being read");
7111
+ }
7112
+ return parseEnvFile(bytes.toString("utf8"));
7113
+ } finally {
7114
+ if (fd !== -1)
7115
+ closeSync(fd);
7116
+ }
6753
7117
  }
6754
- function exactDataRoot() {
6755
- for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
6756
- const dir = process.env[key]?.trim();
6757
- if (dir)
6758
- return resolve(dir);
7118
+ function readCredentialFile(path, apiKeyKeys) {
7119
+ const parsed = readAppConfigFile(path);
7120
+ if (!parsed)
7121
+ return null;
7122
+ for (const key of apiKeyKeys) {
7123
+ if (parsed.unusable.has(key)) {
7124
+ throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
7125
+ }
6759
7126
  }
6760
- return;
7127
+ const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
7128
+ if (new Set(values).size > 1) {
7129
+ throw new CredentialFileUnsafeError(path, "credential aliases disagree");
7130
+ }
7131
+ return values[0] ?? null;
6761
7132
  }
6762
- function hasExactOverride(env = process.env) {
6763
- return Boolean(env[DATA_DIR_ENV]?.trim()) || Boolean(env[HASNA_SKILLS_HOME_ENV]?.trim()) || Boolean(env[SKILLS_HOME_ENV]?.trim());
7133
+ function appConfigDiskValue(name, env, keys) {
7134
+ const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
7135
+ if (wanted.length === 0)
7136
+ return null;
7137
+ for (const path of credentialDiskSources(name, env)) {
7138
+ const parsed = readAppConfigFile(path);
7139
+ if (!parsed)
7140
+ continue;
7141
+ if (wanted.some((key) => parsed.unusable.has(key))) {
7142
+ return { key: wanted.find((key) => parsed.unusable.has(key)), value: "", path, unusable: true };
7143
+ }
7144
+ const values = wanted.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
7145
+ if (new Set(values).size > 1)
7146
+ throw new CredentialFileUnsafeError(path, "configuration aliases disagree");
7147
+ for (const key of wanted) {
7148
+ if (parsed.unusable.has(key))
7149
+ return { key, value: "", path, unusable: true };
7150
+ const value = parsed.values.get(key)?.trim();
7151
+ if (value)
7152
+ return { key, value, path };
7153
+ }
7154
+ }
7155
+ return null;
6764
7156
  }
6765
- function hasOperatorOverride(env = process.env) {
6766
- return hasExactOverride(env) || Boolean(env.HASNA_DATA_HOME?.trim());
7157
+ function assertUsableCredential(appName, source, value) {
7158
+ if (VAULT_POINTER_SHAPE.test(value)) {
7159
+ throw new CredentialResolutionError(appName, `The credential from ${source} looks like a secrets-vault pointer (a path-shaped reference like ` + `'namespace/app/live/api_key'). A vault path is NEVER accepted as a literal API key. ` + `Use ${credentialPointerEnvKey(appName)} to resolve the key through the vault, or provide the actual key value.`, [source]);
7160
+ }
7161
+ if (!ILLEGAL_IN_HEADER_VALUE.test(value))
7162
+ return;
7163
+ throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
7164
+ }
7165
+ function sealCredential(fields) {
7166
+ const { apiKey } = fields;
7167
+ const visible = {
7168
+ tier: fields.tier,
7169
+ source: fields.source,
7170
+ deliberate: fields.deliberate,
7171
+ diskCandidates: Object.freeze([...fields.diskCandidates]),
7172
+ warning: fields.warning
7173
+ };
7174
+ const sealed = { ...visible };
7175
+ Object.defineProperty(sealed, "apiKey", {
7176
+ value: apiKey,
7177
+ enumerable: false,
7178
+ writable: false,
7179
+ configurable: false
7180
+ });
7181
+ if (fields.pointerVaultKey !== undefined) {
7182
+ Object.defineProperty(sealed, "pointerVaultKey", {
7183
+ value: fields.pointerVaultKey,
7184
+ enumerable: false,
7185
+ writable: false,
7186
+ configurable: false
7187
+ });
7188
+ }
7189
+ Object.defineProperty(sealed, INSPECT_CUSTOM, {
7190
+ value: () => ({ ...visible, apiKey: "[redacted]" }),
7191
+ enumerable: false,
7192
+ writable: false,
7193
+ configurable: false
7194
+ });
7195
+ Object.defineProperty(sealed, CREDENTIAL_SEAL, {
7196
+ value: true,
7197
+ enumerable: false,
7198
+ writable: false,
7199
+ configurable: false
7200
+ });
7201
+ return Object.freeze(sealed);
6767
7202
  }
6768
- function getDataRoot() {
6769
- const exact = exactDataRoot();
6770
- if (exact)
6771
- return exact;
6772
- const resolved = resolverDataRoot();
6773
- return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
7203
+ function firstEnvValue(env, keys) {
7204
+ for (const key of keys) {
7205
+ if (!Object.prototype.hasOwnProperty.call(env, key))
7206
+ continue;
7207
+ const value = env[key]?.trim();
7208
+ if (value)
7209
+ return { key, value };
7210
+ }
7211
+ return null;
6774
7212
  }
6775
- var PATHS_RESOLVER_KIND_ENV, PATHS_RESOLVER_APP_SLUG_RE, DATA_DIR_ENV = "HASNA_SKILLS_DIR", HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME", SKILLS_HOME_ENV = "SKILLS_HOME", DEFAULT_SQLITE_FILENAME = "server.db", GLOBAL_CONFIG_FILENAME = "config.json";
6776
- var init_app_home = __esm(() => {
6777
- PATHS_RESOLVER_KIND_ENV = {
6778
- config: "HASNA_CONFIG_HOME",
6779
- data: "HASNA_DATA_HOME",
6780
- state: "HASNA_STATE_HOME",
6781
- cache: "HASNA_CACHE_HOME"
6782
- };
6783
- PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6784
- });
6785
-
6786
- // src/lib/config.ts
6787
- import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
6788
- import { join as join2, dirname } from "path";
6789
- import { homedir as homedir2 } from "os";
6790
- function validKeys() {
6791
- return [...Object.keys(ENUM_KEYS), ...STRING_KEYS];
7213
+ function isAmbientEnvironment(env) {
7214
+ return env === process.env || env[AMBIENT_ENVIRONMENT] === true;
6792
7215
  }
6793
- function allowedValues(key) {
6794
- return ENUM_KEYS[key];
7216
+ function defaultKeychainRunner(argv) {
7217
+ const result = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
7218
+ encoding: "utf8",
7219
+ stdio: ["ignore", "pipe", "pipe"],
7220
+ timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
7221
+ });
7222
+ return {
7223
+ status: result.status,
7224
+ stdout: result.stdout ?? "",
7225
+ stderr: result.error ? result.error.message : result.stderr ?? ""
7226
+ };
6795
7227
  }
6796
- function mergeDirectoryContents(sourceDir, targetDir) {
6797
- if (!existsSync2(sourceDir))
6798
- return;
6799
- mkdirSync(targetDir, { recursive: true });
6800
- for (const entry of readdirSync(sourceDir)) {
6801
- const sourcePath = join2(sourceDir, entry);
6802
- const targetPath = join2(targetDir, entry);
6803
- try {
6804
- const sourceStat = statSync(sourcePath);
6805
- if (sourceStat.isDirectory()) {
6806
- mergeDirectoryContents(sourcePath, targetPath);
6807
- continue;
6808
- }
6809
- if (!existsSync2(targetPath))
6810
- copyFileSync(sourcePath, targetPath);
6811
- } catch {}
7228
+ function keychainTierEnabled(env, options) {
7229
+ if ((options.platform ?? process.platform) !== "darwin")
7230
+ return false;
7231
+ if (options.enabled !== undefined)
7232
+ return options.enabled;
7233
+ return options.run !== undefined || isAmbientEnvironment(env);
7234
+ }
7235
+ function keychainAccount(env, options) {
7236
+ const station = env[KEYCHAIN_STATION_ENV_KEY]?.trim();
7237
+ if (station)
7238
+ return station;
7239
+ const host = (options.hostname ?? osHostname)().split(".")[0]?.trim() ?? "";
7240
+ if (host)
7241
+ return host;
7242
+ const user = env.USER?.trim();
7243
+ return user || null;
7244
+ }
7245
+ function keychainFailureHint(text) {
7246
+ const line = text.split(/\r?\n/).find((entry) => entry.trim().length > 0)?.trim() ?? "";
7247
+ const clean = line.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
7248
+ return clean ? `: ${clean}` : "";
7249
+ }
7250
+ function readKeychainItem(name, env, kind, options) {
7251
+ if (!SAFE_APP_SLUG.test(name) || !keychainTierEnabled(env, options))
7252
+ return null;
7253
+ const account = keychainAccount(env, options);
7254
+ if (!account)
7255
+ return null;
7256
+ const service = `${KEYCHAIN_SERVICE_PREFIX}.${name}.${kind}`;
7257
+ const source = `keychain:${service}@${account}`;
7258
+ const run = options.run ?? defaultKeychainRunner;
7259
+ let result;
7260
+ try {
7261
+ result = run(["find-generic-password", "-a", account, "-s", service, "-w"]);
7262
+ } catch (error2) {
7263
+ const reason = keychainFailureHint(error2 instanceof Error ? error2.message : String(error2));
7264
+ throw new CredentialResolutionError(name, `The Keychain lookup for ${source} could not run${reason}. A Keychain failure is never resolved ` + `around: fix the keychain, or delete the item to fall through to the credential on disk.`, [source]);
6812
7265
  }
6813
- }
6814
- function normalizeConfigValue(key, value) {
6815
- if (typeof value !== "string")
6816
- return;
6817
- const allowed = allowedValues(key);
6818
- if (allowed)
6819
- return allowed.includes(value) ? value : undefined;
6820
- if (key === "apiUrl") {
6821
- try {
6822
- const url = new URL(value);
6823
- if (url.protocol !== "http:" && url.protocol !== "https:")
6824
- return;
6825
- return value.replace(/\/+$/, "");
6826
- } catch {
6827
- return;
7266
+ if (result.status === KEYCHAIN_ITEM_NOT_FOUND_STATUS)
7267
+ return null;
7268
+ if (result.status !== 0) {
7269
+ throw new CredentialResolutionError(name, `The Keychain lookup for ${source} failed (security exited ` + `${result.status ?? "without a status"}${keychainFailureHint(result.stderr)}). A Keychain item that ` + `exists but cannot be read is never resolved around: unlock the keychain, run from a session that ` + `may use it, or delete the item to fall through to the credential on disk.`, [source]);
7270
+ }
7271
+ const value = result.stdout.trim();
7272
+ if (!value) {
7273
+ throw new CredentialResolutionError(name, `${source} exists but holds an empty value; a declared item never falls through to another ` + `identity. Store a value in it or delete the item.`, [source]);
7274
+ }
7275
+ return { value, source };
7276
+ }
7277
+ function keychainConfigValue(name, env, options = {}) {
7278
+ return readKeychainItem(name, env, "api-url", options);
7279
+ }
7280
+ function snapshotClientEnvironment(name, env) {
7281
+ const keys = clientTransportEnvKeys(name);
7282
+ const ambient = isAmbientEnvironment(env);
7283
+ const snapshot = Object.create(null);
7284
+ for (const key of [
7285
+ ...keys.apiUrlKeys,
7286
+ ...keys.apiKeyKeys,
7287
+ credentialOverrideEnvKey(name),
7288
+ credentialPointerEnvKey(name),
7289
+ CREDENTIAL_PROFILE_ENV_KEY,
7290
+ "HOME",
7291
+ HASNA_HOME_ENV_KEY,
7292
+ HASNA_CONFIG_HOME_ENV_KEY,
7293
+ KEYCHAIN_STATION_ENV_KEY,
7294
+ "USER"
7295
+ ]) {
7296
+ const descriptor = Object.getOwnPropertyDescriptor(env, key);
7297
+ if (!descriptor)
7298
+ continue;
7299
+ if (!("value" in descriptor)) {
7300
+ throw new CredentialResolutionError(name, `${key} is accessor-backed; client configuration requires own data properties.`, [key]);
7301
+ }
7302
+ if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
7303
+ throw new CredentialResolutionError(name, `${key} must be a string data property.`, [key]);
6828
7304
  }
7305
+ snapshot[key] = descriptor.value;
6829
7306
  }
6830
- if (key === "extensionsDir")
6831
- return value.trim() ? value : undefined;
6832
- return;
6833
- }
6834
- function isOwnerLayoutMigrated(appDir) {
6835
- return existsSync2(join2(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
7307
+ if (ambient) {
7308
+ Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT, {
7309
+ value: true,
7310
+ enumerable: false,
7311
+ writable: false,
7312
+ configurable: false
7313
+ });
7314
+ }
7315
+ return Object.freeze(snapshot);
7316
+ }
7317
+ function resolveCredential(name, env, options = {}) {
7318
+ env = snapshotClientEnvironment(name, env);
7319
+ const { apiKeyKeys } = clientTransportEnvKeys(name);
7320
+ const diskPaths = credentialDiskSources(name, env);
7321
+ if (options.apiKey !== undefined) {
7322
+ const explicitKey = options.apiKey.trim();
7323
+ if (!explicitKey) {
7324
+ throw new CredentialResolutionError(name, "The explicit apiKey argument is blank; an explicit credential never falls through to another identity.", ["explicit apiKey argument"]);
7325
+ }
7326
+ assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
7327
+ return sealCredential({
7328
+ apiKey: explicitKey,
7329
+ tier: "argument",
7330
+ source: "explicit apiKey argument",
7331
+ deliberate: true,
7332
+ diskCandidates: diskPaths,
7333
+ warning: null
7334
+ });
7335
+ }
7336
+ const overrideKeyName = credentialOverrideEnvKey(name);
7337
+ const overrideRaw = Object.prototype.hasOwnProperty.call(env, overrideKeyName) ? env[overrideKeyName] : undefined;
7338
+ if (overrideRaw !== undefined) {
7339
+ const override = overrideRaw.trim();
7340
+ if (!override) {
7341
+ throw new CredentialResolutionError(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
7342
+ }
7343
+ assertUsableCredential(name, overrideKeyName, override);
7344
+ return sealCredential({
7345
+ apiKey: override,
7346
+ tier: "override",
7347
+ source: overrideKeyName,
7348
+ deliberate: true,
7349
+ diskCandidates: diskPaths,
7350
+ warning: null
7351
+ });
7352
+ }
7353
+ const pointerKeyName = credentialPointerEnvKey(name);
7354
+ const pointerRaw = Object.prototype.hasOwnProperty.call(env, pointerKeyName) ? env[pointerKeyName] : undefined;
7355
+ if (pointerRaw !== undefined) {
7356
+ const pointer = pointerRaw.trim();
7357
+ if (!pointer) {
7358
+ throw new CredentialResolutionError(name, `${pointerKeyName} is set but empty. It is a deliberate vault pointer, so it is not resolved around: ` + `either give it a vault item key or unset it to fall back to the credential on disk.`, [pointerKeyName]);
7359
+ }
7360
+ if (!VAULT_POINTER_SHAPE.test(pointer)) {
7361
+ throw new CredentialResolutionError(name, `${pointerKeyName} must name a vault ITEM KEY (a path-shaped reference like ` + `'namespace/app/live/api_key'), not a credential value. A pointer that carries a literal is refused.`, [pointerKeyName]);
7362
+ }
7363
+ return sealCredential({
7364
+ apiKey: "",
7365
+ pointerVaultKey: pointer,
7366
+ tier: "pointer",
7367
+ source: pointerKeyName,
7368
+ deliberate: true,
7369
+ diskCandidates: diskPaths,
7370
+ warning: null
7371
+ });
7372
+ }
7373
+ if (options.profile !== undefined && !options.profile.trim()) {
7374
+ throw new CredentialResolutionError(name, "The explicit profile argument is blank; an explicit identity selection never falls through.", ["explicit profile argument"]);
7375
+ }
7376
+ const profileRaw = Object.prototype.hasOwnProperty.call(env, CREDENTIAL_PROFILE_ENV_KEY) ? env[CREDENTIAL_PROFILE_ENV_KEY] : undefined;
7377
+ if (profileRaw !== undefined && !profileRaw.trim()) {
7378
+ throw new CredentialResolutionError(name, `${CREDENTIAL_PROFILE_ENV_KEY} is set but blank.`, [CREDENTIAL_PROFILE_ENV_KEY]);
7379
+ }
7380
+ const profile = options.profile?.trim() || profileRaw?.trim();
7381
+ if (profile) {
7382
+ const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
7383
+ if (!SAFE_PROFILE.test(profile)) {
7384
+ throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
7385
+ }
7386
+ const paths = profileDiskSources(name, env, profile);
7387
+ for (const path of paths) {
7388
+ const value = readCredentialFile(path, apiKeyKeys);
7389
+ if (value) {
7390
+ assertUsableCredential(name, path, value);
7391
+ return sealCredential({
7392
+ apiKey: value,
7393
+ tier: "profile",
7394
+ source: path,
7395
+ deliberate: true,
7396
+ diskCandidates: paths,
7397
+ warning: null
7398
+ });
7399
+ }
7400
+ }
7401
+ throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
7402
+ }
7403
+ const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
7404
+ const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
7405
+ if (blankEnv) {
7406
+ throw new CredentialResolutionError(name, `${blankEnv.key} is set but blank; a declared credential never falls through to another alias or identity.`, [blankEnv.key]);
7407
+ }
7408
+ if (definedEnvEntries.length > 1 && new Set(definedEnvEntries.map((entry) => entry.value)).size > 1) {
7409
+ throw new CredentialResolutionError(name, `${definedEnvEntries.map((entry) => entry.key).join(" and ")} disagree; credential aliases must be identical or only one may be set.`, definedEnvEntries.map((entry) => entry.key));
7410
+ }
7411
+ const envHit = firstEnvValue(env, apiKeyKeys);
7412
+ const keychainHit = readKeychainItem(name, env, "api-key", options.keychain ?? {});
7413
+ if (keychainHit) {
7414
+ assertUsableCredential(name, keychainHit.source, keychainHit.value);
7415
+ const warning = envHit && envHit.value !== keychainHit.value ? `Credential sources disagree for '${name}': ${keychainHit.source} and ${envHit.key} hold ` + `different keys. ${keychainHit.source} wins, because the Keychain is re-read on every call while ` + `an environment variable is a snapshot. Reconcile them \u2014 a rotation that updated only one leaves ` + `the other to fail 401 wherever it is loaded first.` : null;
7416
+ return sealCredential({
7417
+ apiKey: keychainHit.value,
7418
+ tier: "keychain",
7419
+ source: keychainHit.source,
7420
+ deliberate: false,
7421
+ diskCandidates: diskPaths,
7422
+ warning
7423
+ });
7424
+ }
7425
+ const diskSourceList = credentialDiskSourceList(name, env, null);
7426
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
7427
+ if (diskHits.length > 0) {
7428
+ const winner = diskHits[0];
7429
+ assertUsableCredential(name, winner.src.path, winner.value);
7430
+ const divergentSources = [
7431
+ ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
7432
+ ...envHit && envHit.value !== winner.value ? [envHit.key] : []
7433
+ ];
7434
+ const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
7435
+ return sealCredential({
7436
+ apiKey: winner.value,
7437
+ tier: winner.src.tier,
7438
+ source: winner.src.path,
7439
+ deliberate: false,
7440
+ diskCandidates: diskPaths,
7441
+ warning
7442
+ });
7443
+ }
7444
+ if (envHit) {
7445
+ assertUsableCredential(name, envHit.key, envHit.value);
7446
+ return sealCredential({
7447
+ apiKey: envHit.value,
7448
+ tier: "env",
7449
+ source: envHit.key,
7450
+ deliberate: false,
7451
+ diskCandidates: diskPaths,
7452
+ warning: null
7453
+ });
7454
+ }
7455
+ return null;
6836
7456
  }
6837
- function getDataDir() {
6838
- const root = getDataRoot();
7457
+ async function completePointerCredential(name, pointerResolution, env = process.env) {
7458
+ const vaultKey = pointerResolution.pointerVaultKey;
7459
+ const pointerEnvKey = pointerResolution.source;
7460
+ if (!vaultKey) {
7461
+ throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
7462
+ }
7463
+ let secretsSdk;
6839
7464
  try {
6840
- mkdirSync(root, { recursive: true });
6841
- } catch {}
6842
- if (hasOperatorOverride())
6843
- return root;
6844
- const home = effectiveHome();
6845
- const oldDir = join2(home, ".skills");
6846
- const oldConfigFile = join2(home, ".skillsrc");
7465
+ secretsSdk = requireSecretsSdk(SECRETS_PACKAGE_SPECIFIER);
7466
+ } catch {
7467
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
7468
+ }
7469
+ let client;
6847
7470
  try {
6848
- mergeDirectoryContents(oldDir, root);
6849
- } catch {}
6850
- if (existsSync2(oldConfigFile) && !existsSync2(join2(root, "config.json"))) {
6851
- try {
6852
- copyFileSync(oldConfigFile, join2(root, "config.json"));
6853
- } catch {}
7471
+ client = secretsSdk.createSecretsClientFromEnv(env);
7472
+ } catch {
7473
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
6854
7474
  }
6855
- return root;
7475
+ let secret;
7476
+ try {
7477
+ secret = await client.getSecret({ key: vaultKey });
7478
+ } catch {
7479
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the vault could not be reached or the item is ` + `unavailable. A vault pointer is TERMINAL and never falls through to a literal or disk credential.`, [pointerEnvKey]);
7480
+ }
7481
+ const value = secret.value;
7482
+ if (!value) {
7483
+ throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
7484
+ }
7485
+ assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
7486
+ return sealCredential({
7487
+ apiKey: value,
7488
+ tier: "pointer",
7489
+ source: `${pointerEnvKey} -> vault:${vaultKey}`,
7490
+ deliberate: true,
7491
+ diskCandidates: pointerResolution.diskCandidates,
7492
+ warning: null
7493
+ });
6856
7494
  }
6857
- function getDataDirReadOnly() {
6858
- return getDataRoot();
7495
+ function defaultFleetGatewayBaseUrl(name) {
7496
+ return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
6859
7497
  }
6860
- function getConfigPathReadOnly(scope) {
6861
- if (scope === "global")
6862
- return join2(getDataDirReadOnly(), "config.json");
6863
- return join2(process.cwd(), "skills.config.json");
7498
+ function isValidDnsDomain(value) {
7499
+ if (value.length === 0 || value.length > 253 || ASCII_CONTROL_PATTERN.test(value) || /[^\x00-\x7f]/.test(value)) {
7500
+ return false;
7501
+ }
7502
+ return value.split(".").every((label) => label.length <= 63 && !label.startsWith("xn--") && DNS_LABEL_PATTERN.test(label));
6864
7503
  }
6865
- function loadConfigReadOnly() {
6866
- const canonicalConfigPath = getConfigPathReadOnly("global");
6867
- let globalConfig2;
6868
- if (existsSync2(canonicalConfigPath)) {
6869
- globalConfig2 = readConfigFile(canonicalConfigPath);
6870
- } else if (hasOperatorOverride()) {
6871
- globalConfig2 = {};
6872
- } else {
6873
- globalConfig2 = readConfigFile(legacyConfigFilePath());
7504
+ function validateAppSlug(name) {
7505
+ if (name.length > 63 || !DNS_LABEL_PATTERN.test(name)) {
7506
+ throw new Error("App name must be one lowercase DNS label.");
6874
7507
  }
6875
- const projectConfig = readConfigFile(getConfigPathReadOnly("project"));
6876
- return { ...globalConfig2, ...projectConfig };
7508
+ return name;
6877
7509
  }
6878
- function legacyConfigFilePath() {
6879
- return join2(process.env["HOME"] || process.env["USERPROFILE"] || homedir2(), ".skillsrc");
7510
+ function rawAuthority(value) {
7511
+ const match = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
7512
+ if (!match)
7513
+ throw new Error("API URL must be absolute.");
7514
+ const afterScheme = value.slice(match[0].length);
7515
+ const boundary = afterScheme.search(/[/?#]/);
7516
+ const authority = boundary === -1 ? afterScheme : afterScheme.slice(0, boundary);
7517
+ if (!authority)
7518
+ throw new Error("API URL must include a hostname.");
7519
+ return authority;
6880
7520
  }
6881
- function getConfigPath(scope) {
6882
- if (scope === "global") {
6883
- return join2(getDataDir(), "config.json");
7521
+ function assertCanonicalPort(port) {
7522
+ if (!/^[0-9]+$/.test(port) || port.length > 1 && port.startsWith("0")) {
7523
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
6884
7524
  }
6885
- return join2(process.cwd(), "skills.config.json");
6886
- }
6887
- function readConfigFile(path) {
6888
- if (!existsSync2(path))
6889
- return {};
6890
- let parsed;
6891
- try {
6892
- parsed = JSON.parse(readFileSync(path, "utf-8"));
6893
- } catch {
6894
- return {};
7525
+ const numericPort = Number(port);
7526
+ if (!Number.isSafeInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
7527
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
6895
7528
  }
6896
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
6897
- return {};
6898
- assertNoRetiredConfigKeys(parsed, path);
6899
- const config2 = {};
6900
- for (const key of validKeys()) {
6901
- const value = normalizeConfigValue(key, parsed[key]);
6902
- if (value !== undefined)
6903
- config2[key] = value;
7529
+ }
7530
+ function canonicalAuthorityHostname(authority) {
7531
+ let rawHostname;
7532
+ if (authority.startsWith("[")) {
7533
+ const closingBracket = authority.indexOf("]");
7534
+ if (closingBracket === -1) {
7535
+ throw new Error("API URL authority must contain a canonical hostname.");
7536
+ }
7537
+ rawHostname = authority.slice(0, closingBracket + 1);
7538
+ const portSuffix = authority.slice(closingBracket + 1);
7539
+ if (portSuffix) {
7540
+ if (!portSuffix.startsWith(":")) {
7541
+ throw new Error("API URL authority must contain a canonical hostname and port.");
7542
+ }
7543
+ assertCanonicalPort(portSuffix.slice(1));
7544
+ }
7545
+ if (isIP(rawHostname.slice(1, -1)) !== 6) {
7546
+ throw new Error("API URL authority must contain a canonical IPv6 literal.");
7547
+ }
7548
+ } else {
7549
+ const firstColon = authority.indexOf(":");
7550
+ const lastColon = authority.lastIndexOf(":");
7551
+ if (firstColon !== lastColon) {
7552
+ throw new Error("IPv6 API URL authorities must use brackets.");
7553
+ }
7554
+ if (lastColon !== -1) {
7555
+ const port = authority.slice(lastColon + 1);
7556
+ assertCanonicalPort(port);
7557
+ rawHostname = authority.slice(0, lastColon);
7558
+ } else {
7559
+ rawHostname = authority;
7560
+ }
7561
+ const ipVersion = isIP(rawHostname);
7562
+ const numericAddressParts = rawHostname.split(".");
7563
+ const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
7564
+ if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
7565
+ throw new Error("API URL authority must contain a canonical ASCII hostname.");
7566
+ }
6904
7567
  }
6905
- return config2;
7568
+ return rawHostname.toLowerCase();
6906
7569
  }
6907
- function loadConfig() {
6908
- const globalConfig2 = readConfigFile(getConfigPath("global"));
6909
- const projectConfig = readConfigFile(getConfigPath("project"));
6910
- return { ...globalConfig2, ...projectConfig };
7570
+ function isDeliberateLoopbackHttpAuthority(authority) {
7571
+ return /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(authority);
6911
7572
  }
6912
- var ENUM_KEYS, STRING_KEYS, INSTALLED_SKILLS_DIRNAME = "installed", SKILLS_CACHE_DIRNAME = "skills", LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
6913
- var init_config = __esm(() => {
6914
- init_retired_settings();
6915
- init_app_home();
6916
- init_app_home();
6917
- ENUM_KEYS = {
6918
- defaultAgent: ["claude", "codex", "gemini", "pi", "opencode", "all"],
6919
- defaultScope: ["global", "project"],
6920
- format: ["compact", "json", "csv"]
6921
- };
6922
- STRING_KEYS = ["apiUrl", "extensionsDir"];
6923
- });
6924
-
6925
- // src/lib/remote-run-contract.ts
6926
- function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
6927
- const record3 = isRecord3(payload) ? payload : {};
7573
+ function toV1BaseUrl(apiUrl) {
7574
+ if (ASCII_CONTROL_PATTERN.test(apiUrl)) {
7575
+ throw new Error("API URL must not contain ASCII control characters.");
7576
+ }
7577
+ const input = apiUrl.trim();
7578
+ const authority = rawAuthority(input);
7579
+ if (authority.includes("@") || authority.includes("\\") || authority.includes("%") || /[^\x00-\x7f]/.test(authority)) {
7580
+ throw new Error("API URL authority must be canonical ASCII without credentials.");
7581
+ }
7582
+ const canonicalHostname = canonicalAuthorityHostname(authority);
7583
+ const url = new URL(input);
7584
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
7585
+ throw new Error("API URL must use http or https.");
7586
+ }
7587
+ if (url.username || url.password) {
7588
+ throw new Error("API URL must not include credentials.");
7589
+ }
7590
+ if (!url.hostname || url.hostname.endsWith(".")) {
7591
+ throw new Error("API URL must include a canonical hostname.");
7592
+ }
7593
+ if (url.hostname.toLowerCase() !== canonicalHostname) {
7594
+ throw new Error("API URL authority must not rely on parser hostname normalization.");
7595
+ }
7596
+ if (url.hostname.split(".").some((label) => label.toLowerCase().startsWith("xn--"))) {
7597
+ throw new Error("API URL must not use IDN or punycode hostnames.");
7598
+ }
7599
+ if (url.protocol === "http:" && !isDeliberateLoopbackHttpAuthority(authority)) {
7600
+ throw new Error("API URL may use http only for an exact loopback authority.");
7601
+ }
7602
+ if (url.search || url.hash) {
7603
+ throw new Error("API URL must not include a query string or fragment.");
7604
+ }
7605
+ let path = url.pathname.replace(/\/+$/, "");
7606
+ if (path.endsWith("/v1"))
7607
+ path = path.slice(0, -"/v1".length);
7608
+ url.pathname = `${path}/v1`;
7609
+ return url.toString().replace(/\/+$/, "");
7610
+ }
7611
+ function resolveClientTransportSnapshot(name, env = process.env, options = {}) {
7612
+ env = snapshotClientEnvironment(name, env);
7613
+ const keys = clientTransportEnvKeys(name);
7614
+ const definedUrlEntries = keys.apiUrlKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, raw: String(env[key]) }));
7615
+ const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
7616
+ if (blankUrl) {
7617
+ throw new ClientTransportConfigurationError(name, `${blankUrl.key} is set but blank; public clients require an explicit HTTPS API URL and never select local storage.`, [blankUrl.key]);
7618
+ }
7619
+ const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN.test(entry.raw));
7620
+ if (controlledUrl) {
7621
+ throw new ClientTransportConfigurationError(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
7622
+ }
7623
+ const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
7624
+ if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map((entry) => entry.value)).size > 1) {
7625
+ throw new ClientTransportConfigurationError(name, `${usableUrlEntries.map((entry) => entry.key).join(" and ")} disagree; client authority aliases must be identical or only one may be set.`, usableUrlEntries.map((entry) => entry.key));
7626
+ }
7627
+ const envUrlHit = usableUrlEntries[0] ?? null;
7628
+ const keychainUrlHit = keychainConfigValue(name, env, options.credentials?.keychain);
7629
+ const diskConfigUrlHit = appConfigDiskValue(name, env, keys.apiUrlKeys);
7630
+ if (diskConfigUrlHit?.unusable) {
7631
+ throw new ClientTransportConfigurationError(name, `${diskConfigUrlHit.key} in ${diskConfigUrlHit.path} is declared but blank or malformed; public clients require a valid HTTPS service authority.`, [diskConfigUrlHit.path]);
7632
+ }
7633
+ const urlCandidates = [
7634
+ ...envUrlHit ? [envUrlHit] : [],
7635
+ ...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
7636
+ ...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
7637
+ ];
7638
+ const configuredUrl = urlCandidates[0] ?? null;
7639
+ const divergentUrls = urlCandidates.filter((candidate) => candidate.value !== configuredUrl?.value);
7640
+ if (configuredUrl && divergentUrls.length > 0) {
7641
+ throw new ClientTransportConfigurationError(name, `${configuredUrl.key} and ${divergentUrls.map((candidate) => candidate.key).join(" and ")} select different service authorities; refusing to send a credential written for one authority to the other.`, urlCandidates.map((candidate) => candidate.key));
7642
+ }
7643
+ const warnings = [];
7644
+ if (configuredUrl && !envUrlHit) {
7645
+ warnings.push(`No ${keys.apiUrlKeys[0]} in the environment; the server URL in ${configuredUrl.key} was used, so this client connects to the server. ` + `Keep that entry aligned with the intended service authority.`);
7646
+ }
7647
+ const credential = resolveCredential(name, env, options.credentials);
7648
+ if (!credential) {
7649
+ const diskHint = credentialDiskSourcesForMessage(name, env);
7650
+ const lead = configuredUrl ? `${configuredUrl.key} selects the HTTP server for '${name}', but no API key could be resolved` : `${keys.apiUrlKeys[0]} is not set and no API key could be resolved for '${name}'; a credential is required before the default fleet gateway authority applies`;
7651
+ warnings.push(`${lead}; refusing to create an unauthenticated client \u2014 public clients never fall back to SQLite or another local store. ` + `Looked in the Keychain (macOS only), then for a credential file at ${diskHint}, then for ${keys.apiKeyKeys[0]} in the environment.`);
7652
+ throw new ClientTransportConfigurationError(name, warnings.join(" "), [configuredUrl?.key ?? keys.apiUrlKeys[0]]);
7653
+ }
7654
+ if (credential.warning)
7655
+ warnings.push(credential.warning);
7656
+ let urlHit;
7657
+ if (configuredUrl) {
7658
+ urlHit = configuredUrl;
7659
+ } else {
7660
+ try {
7661
+ urlHit = { key: DEFAULT_AUTHORITY_SOURCE, value: defaultFleetGatewayBaseUrl(name) };
7662
+ } catch (error2) {
7663
+ const message = error2 instanceof Error ? error2.message : String(error2);
7664
+ throw new ClientTransportConfigurationError(name, `No ${keys.apiUrlKeys[0]} is configured and the default fleet gateway authority cannot be composed for '${name}': ${message}`, [keys.apiUrlKeys[0]]);
7665
+ }
7666
+ }
7667
+ const apiUrlSource = urlHit.key;
7668
+ let baseUrl;
7669
+ try {
7670
+ baseUrl = toV1BaseUrl(urlHit.value);
7671
+ } catch (error2) {
7672
+ const message = error2 instanceof Error ? error2.message : String(error2);
7673
+ throw new ClientTransportConfigurationError(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
7674
+ }
6928
7675
  return {
6929
- contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
6930
- ...pickString(record3, "id"),
6931
- skill: pickStringValue(record3, "skill") ?? fallbackSkill,
6932
- ...pickString(record3, "requestedSlug"),
6933
- ...pickString(record3, "status"),
6934
- ...pickNumber(record3, "exitCode"),
6935
- ...pickString(record3, "correlationId"),
6936
- ...pickString(record3, "createdAt"),
6937
- ...pickString(record3, "startedAt"),
6938
- ...pickString(record3, "completedAt"),
6939
- ...pickNumber(record3, "durationMs"),
6940
- ...pickString(record3, "outputType"),
6941
- ...hasOwn(record3, "outputPreview") ? { outputPreview: record3.outputPreview } : {},
6942
- ...pickString(record3, "errorCode"),
6943
- ...pickString(record3, "errorMessage"),
6944
- ...pickString(record3, "error"),
6945
- ...pickString(record3, "code"),
6946
- ...hasOwn(record3, "details") ? { details: record3.details } : {}
7676
+ resolution: {
7677
+ transport: "http",
7678
+ transportSource: urlHit.key,
7679
+ baseUrl,
7680
+ apiUrlSource,
7681
+ apiKeyPresent: true,
7682
+ apiKeySource: credential.source,
7683
+ apiKeyTier: credential.tier,
7684
+ misconfigured: false,
7685
+ warning: warnings.length > 0 ? warnings.join(" ") : null
7686
+ },
7687
+ credential
6947
7688
  };
6948
7689
  }
6949
- function isRecord3(value) {
6950
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
6951
- }
6952
- function hasOwn(record3, key) {
6953
- return Object.prototype.hasOwnProperty.call(record3, key);
7690
+ function resolveClientTransport(name, env = process.env, options = {}) {
7691
+ return resolveClientTransportSnapshot(name, env, options).resolution;
6954
7692
  }
6955
- function pickString(record3, key) {
6956
- const value = pickStringValue(record3, key);
6957
- return value === undefined ? {} : { [key]: value };
7693
+ function credentialDiskSourcesForMessage(name, env) {
7694
+ const paths = credentialDiskSources(name, env);
7695
+ return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
6958
7696
  }
6959
- function pickStringValue(record3, key) {
6960
- const value = record3[key];
6961
- return typeof value === "string" ? value : undefined;
6962
- }
6963
- function pickNumber(record3, key) {
6964
- const value = record3[key];
6965
- return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
6966
- }
6967
- var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
6968
-
6969
- // src/lib/api-url.ts
6970
- function resolveApiUrl(config2 = loadConfig(), env = process.env) {
6971
- const raw = env[API_URL_ENV_VAR] || config2[API_URL_CONFIG_KEY];
6972
- const trimmed = raw?.trim().replace(/\/+$/, "");
6973
- return trimmed || undefined;
6974
- }
6975
- function requireApiUrl(action = "This command", config2, env) {
6976
- const resolved = resolveApiUrl(config2 ?? loadConfig(), env ?? process.env);
6977
- if (!resolved)
6978
- throw new MissingApiUrlError(action);
6979
- return resolved;
6980
- }
6981
- var API_URL_ENV_VAR = "SKILLS_API_URL", API_URL_CONFIG_KEY = "apiUrl", MISSING_API_URL_HINT, MissingApiUrlError;
6982
- var init_api_url = __esm(() => {
6983
- init_config();
6984
- MISSING_API_URL_HINT = `set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
6985
- MissingApiUrlError = class MissingApiUrlError extends Error {
6986
- code = "MISSING_API_URL";
6987
- constructor(action = "This command") {
6988
- super(`${action} requires a Skills API URL and none is configured \u2014 ${MISSING_API_URL_HINT}`);
6989
- this.name = "MissingApiUrlError";
7697
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
7698
+ var init_transport = __esm(() => {
7699
+ CredentialResolutionError = class CredentialResolutionError extends Error {
7700
+ appName;
7701
+ attempted;
7702
+ constructor(appName, message, attempted) {
7703
+ super(message);
7704
+ this.name = "CredentialResolutionError";
7705
+ this.appName = appName;
7706
+ this.attempted = attempted;
7707
+ }
7708
+ };
7709
+ CredentialFileUnsafeError = class CredentialFileUnsafeError extends Error {
7710
+ path;
7711
+ constructor(path, reason) {
7712
+ super(`Refusing unsafe credential/config file ${path}: ${reason}.`);
7713
+ this.name = "CredentialFileUnsafeError";
7714
+ this.path = path;
6990
7715
  }
6991
7716
  };
7717
+ MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
7718
+ SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
7719
+ SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
7720
+ ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
7721
+ VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
7722
+ CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
7723
+ INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
7724
+ CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
7725
+ AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
7726
+ SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
7727
+ requireSecretsSdk = createRequire(import.meta.url);
7728
+ ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
7729
+ DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
7730
+ ClientTransportConfigurationError = class ClientTransportConfigurationError extends Error {
7731
+ appName;
7732
+ sources;
7733
+ constructor(appName, message, sources = []) {
7734
+ super(message);
7735
+ this.name = "ClientTransportConfigurationError";
7736
+ this.appName = appName;
7737
+ this.sources = Object.freeze([...sources]);
7738
+ }
7739
+ };
7740
+ IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
7741
+ AUTHORITY_OVERRIDE_HEADERS = new Set([
7742
+ "host",
7743
+ ":authority",
7744
+ "forwarded",
7745
+ "x-forwarded-host",
7746
+ "x-original-host"
7747
+ ]);
6992
7748
  });
6993
7749
 
6994
- // src/lib/auth-store.ts
6995
- var exports_auth_store = {};
6996
- __export(exports_auth_store, {
6997
- saveAuthConfig: () => saveAuthConfig,
7750
+ // src/lib/fleet-credentials.ts
7751
+ var exports_fleet_credentials = {};
7752
+ __export(exports_fleet_credentials, {
7753
+ skillsCredentialOrReason: () => skillsCredentialOrReason,
7754
+ skillsCredentialFiles: () => skillsCredentialFiles,
7755
+ skillsCredentialFilePath: () => skillsCredentialFilePath,
7756
+ resolveSkillsFleet: () => resolveSkillsFleet,
7757
+ resolveSkillsApiOrigin: () => resolveSkillsApiOrigin,
7758
+ resolveSkillsApiKey: () => resolveSkillsApiKey,
7759
+ resetLocalSkillsModeNotice: () => resetLocalSkillsModeNotice,
7760
+ requireSkillsFleet: () => requireSkillsFleet,
7761
+ requireSkillsApiOrigin: () => requireSkillsApiOrigin,
7762
+ requireSkillsApiKey: () => requireSkillsApiKey,
7763
+ noticeLocalSkillsMode: () => noticeLocalSkillsMode,
6998
7764
  normalizeSkillsApiOrigin: () => normalizeSkillsApiOrigin,
6999
- getAuthFilePathReadOnly: () => getAuthFilePathReadOnly,
7000
- getAuthFilePath: () => getAuthFilePath,
7001
- getAuthConfigReadOnly: () => getAuthConfigReadOnly,
7002
- getAuthConfig: () => getAuthConfig,
7003
- getApiUrl: () => getApiUrl,
7004
- getApiKeyReadOnly: () => getApiKeyReadOnly,
7005
- getApiKey: () => getApiKey,
7006
- clearAuthConfig: () => clearAuthConfig
7007
- });
7008
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6, unlinkSync } from "fs";
7009
- import { dirname as dirname5, join as join13 } from "path";
7010
- import { homedir as homedir4 } from "os";
7011
- function getAuthFilePath() {
7012
- return join13(getDataDir(), "auth.json");
7013
- }
7014
- function getAuthFilePathReadOnly() {
7015
- return join13(getDataDirReadOnly(), "auth.json");
7016
- }
7017
- function legacyAuthFilePath() {
7018
- return join13(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
7019
- }
7020
- function getAuthConfig() {
7021
- if (cachedConfig !== undefined)
7022
- return cachedConfig;
7023
- try {
7024
- const file = existsSync13(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
7025
- const raw = readFileSync11(file, "utf-8");
7026
- const config2 = JSON.parse(raw);
7027
- if (!config2.apiKey) {
7028
- cachedConfig = null;
7029
- return null;
7030
- }
7031
- cachedConfig = config2;
7032
- return config2;
7033
- } catch {
7034
- cachedConfig = null;
7035
- return null;
7036
- }
7037
- }
7038
- function saveAuthConfig(config2) {
7039
- const file = getAuthFilePath();
7040
- mkdirSync6(dirname5(file), { recursive: true, mode: 448 });
7041
- writeFileSync6(file, JSON.stringify(config2, null, 2) + `
7042
- `, { mode: 384 });
7043
- cachedConfig = config2;
7044
- }
7045
- function clearAuthConfig() {
7046
- try {
7047
- unlinkSync(getAuthFilePath());
7048
- } catch {}
7049
- try {
7050
- unlinkSync(legacyAuthFilePath());
7051
- } catch {}
7052
- cachedConfig = undefined;
7053
- }
7054
- function getApiKey() {
7055
- if (process.env.SKILLS_API_KEY)
7056
- return process.env.SKILLS_API_KEY;
7057
- if (process.env.SKILL_API_KEY)
7058
- return process.env.SKILL_API_KEY;
7059
- return getAuthConfig()?.apiKey || null;
7060
- }
7061
- function getAuthConfigReadOnly() {
7062
- try {
7063
- const file = existsSync13(getAuthFilePathReadOnly()) ? getAuthFilePathReadOnly() : legacyAuthFilePath();
7064
- const raw = readFileSync11(file, "utf-8");
7065
- const config2 = JSON.parse(raw);
7066
- if (!config2.apiKey)
7067
- return null;
7068
- return config2;
7069
- } catch {
7765
+ configuredSkillsApiUrl: () => configuredSkillsApiUrl,
7766
+ SkillsFleetCredentialError: () => SkillsFleetCredentialError,
7767
+ SKILLS_APP: () => SKILLS_APP,
7768
+ SKILLS_API_URL_ENV_KEYS: () => SKILLS_API_URL_ENV_KEYS,
7769
+ SKILLS_API_URL_ENV: () => SKILLS_API_URL_ENV,
7770
+ SKILLS_API_KEY_ENV_KEYS: () => SKILLS_API_KEY_ENV_KEYS,
7771
+ SKILLS_API_KEY_ENV: () => SKILLS_API_KEY_ENV,
7772
+ MissingSkillsFleetError: () => MissingSkillsFleetError
7773
+ });
7774
+ function isClientTransportConfigurationError(error2) {
7775
+ return error2 instanceof ClientTransportConfigurationError || typeof error2 === "object" && error2 !== null && error2.name === "ClientTransportConfigurationError";
7776
+ }
7777
+ function isCredentialResolutionError(error2) {
7778
+ return error2 instanceof CredentialResolutionError || typeof error2 === "object" && error2 !== null && error2.name === "CredentialResolutionError";
7779
+ }
7780
+ function asSkillsFleetCredentialError(error2) {
7781
+ if (!isCredentialResolutionError(error2))
7070
7782
  return null;
7071
- }
7072
- }
7073
- function getApiKeyReadOnly() {
7074
- if (process.env.SKILLS_API_KEY)
7075
- return process.env.SKILLS_API_KEY;
7076
- if (process.env.SKILL_API_KEY)
7077
- return process.env.SKILL_API_KEY;
7078
- return getAuthConfigReadOnly()?.apiKey || null;
7783
+ return new SkillsFleetCredentialError(error2.message, "MISSING_API_CREDENTIAL");
7079
7784
  }
7080
7785
  function normalizeSkillsApiOrigin(apiUrl) {
7081
7786
  const url = new URL(apiUrl);
@@ -7089,13 +7794,182 @@ function normalizeSkillsApiOrigin(apiUrl) {
7089
7794
  }
7090
7795
  return url.toString().replace(/\/+$/, "");
7091
7796
  }
7092
- function getApiUrl(action) {
7093
- return normalizeSkillsApiOrigin(requireApiUrl(action));
7797
+ function configuredSkillsApiUrl(env = process.env, keychain) {
7798
+ for (const key of SKILLS_API_URL_ENV_KEYS) {
7799
+ const value = env[key]?.trim();
7800
+ if (value)
7801
+ return { value, source: key };
7802
+ }
7803
+ const fromKeychain = keychainConfigValue(SKILLS_APP, env, keychain);
7804
+ if (fromKeychain)
7805
+ return { value: fromKeychain.value.trim(), source: fromKeychain.source };
7806
+ const fromDisk = appConfigDiskValue(SKILLS_APP, env, SKILLS_API_URL_ENV_KEYS);
7807
+ if (fromDisk?.unusable) {
7808
+ throw new SkillsFleetCredentialError(`${fromDisk.key} in ${fromDisk.path} is declared but blank or malformed; ` + `a Skills authority must be a valid https URL (or an exact loopback http URL).`, "INVALID_API_URL");
7809
+ }
7810
+ if (fromDisk)
7811
+ return { value: fromDisk.value.trim(), source: fromDisk.path };
7812
+ return null;
7094
7813
  }
7095
- var cachedConfig;
7096
- var init_auth_store = __esm(() => {
7097
- init_api_url();
7098
- init_config();
7814
+ function skillsCredentialFiles(env = process.env) {
7815
+ return credentialDiskSources(SKILLS_APP, env);
7816
+ }
7817
+ function skillsCredentialFilePath(env = process.env) {
7818
+ const paths = skillsCredentialFiles(env);
7819
+ const path = paths[0];
7820
+ if (!path) {
7821
+ throw new Error("No home directory is set (HOME or HASNA_HOME), so there is nowhere to store a Skills credential.");
7822
+ }
7823
+ return path;
7824
+ }
7825
+ function noticeLocalSkillsMode(write = (line) => console.error(line)) {
7826
+ if (localNoticePrinted)
7827
+ return;
7828
+ localNoticePrinted = true;
7829
+ write(`skills: local mode \u2014 no ${SKILLS_API_KEY_ENV} and no ${SKILLS_API_URL_ENV} resolved, ` + `so this runs on this machine against the bundled corpus. ` + `Sign in with: skills auth login`);
7830
+ }
7831
+ function resetLocalSkillsModeNotice() {
7832
+ localNoticePrinted = false;
7833
+ }
7834
+ function resolveSkillsFleet(env = process.env, options = {}) {
7835
+ try {
7836
+ return resolveSkillsFleetOrThrow(env, options);
7837
+ } catch (error2) {
7838
+ const translated = asSkillsFleetCredentialError(error2);
7839
+ if (translated)
7840
+ throw translated;
7841
+ throw error2;
7842
+ }
7843
+ }
7844
+ function resolveSkillsFleetOrThrow(env, options) {
7845
+ let resolution;
7846
+ try {
7847
+ resolution = resolveClientTransport(SKILLS_APP, env, { credentials: options.credentials });
7848
+ } catch (error2) {
7849
+ if (!isClientTransportConfigurationError(error2))
7850
+ throw error2;
7851
+ const configured2 = configuredSkillsApiUrl(env, options.credentials?.keychain);
7852
+ const credential2 = resolveCredential(SKILLS_APP, env, options.credentials);
7853
+ if (!configured2 && !credential2) {
7854
+ if (env === process.env)
7855
+ noticeLocalSkillsMode();
7856
+ return { mode: "local", apiOrigin: null, apiKey: null };
7857
+ }
7858
+ if (configured2 && !credential2) {
7859
+ throw new SkillsFleetCredentialError(`${configured2.source} points this CLI at a Skills service but no API key resolved \u2014 ` + `refusing to run locally instead. Looked in the Keychain item ` + `hasna.credentials.${SKILLS_APP}.api-key, then ${skillsCredentialFiles(env).join(" or ") || "no credentials file (no HOME)"}, ` + `then ${SKILLS_API_KEY_ENV}. Sign in with: skills auth login`);
7860
+ }
7861
+ throw error2;
7862
+ }
7863
+ const configured = configuredSkillsApiUrl(env, options.credentials?.keychain);
7864
+ const apiOrigin = configured ? normalizeSkillsApiOrigin(configured.value) : stripV1(resolution.baseUrl);
7865
+ const credential = resolveCredential(SKILLS_APP, env, options.credentials);
7866
+ if (!credential) {
7867
+ throw new SkillsFleetCredentialError(`A Skills authority resolved but no API key did. Sign in with: skills auth login`);
7868
+ }
7869
+ const base = {
7870
+ mode: "hosted",
7871
+ apiOrigin,
7872
+ apiUrlSource: resolution.apiUrlSource ?? (configured?.source ?? "default"),
7873
+ apiKeySource: resolution.apiKeySource ?? credential.source,
7874
+ apiKeyTier: resolution.apiKeyTier,
7875
+ warning: resolution.warning
7876
+ };
7877
+ if (credential.tier === "pointer") {
7878
+ return { ...base, apiKey: null, apiKeyPointer: credential };
7879
+ }
7880
+ if (!credential.apiKey.trim()) {
7881
+ throw new SkillsFleetCredentialError(`The Skills API key from ${credential.source} is empty \u2014 refusing to send an unauthenticated request. ` + `Sign in with: skills auth login`);
7882
+ }
7883
+ return { ...base, apiKey: credential.apiKey, apiKeyPointer: null };
7884
+ }
7885
+ async function resolveSkillsApiKey(env = process.env, options = {}) {
7886
+ const fleet = resolveSkillsFleet(env, options);
7887
+ if (fleet.mode !== "hosted")
7888
+ return null;
7889
+ if (fleet.apiKey)
7890
+ return fleet.apiKey;
7891
+ const pointer = fleet.apiKeyPointer;
7892
+ if (!pointer) {
7893
+ throw new SkillsFleetCredentialError(`A Skills authority resolved but no API key did. Sign in with: skills auth login`);
7894
+ }
7895
+ let completed;
7896
+ try {
7897
+ completed = await completePointerCredential(SKILLS_APP, pointer, env);
7898
+ } catch (error2) {
7899
+ const translated = asSkillsFleetCredentialError(error2);
7900
+ if (translated)
7901
+ throw translated;
7902
+ throw error2;
7903
+ }
7904
+ if (!completed.apiKey?.trim()) {
7905
+ throw new SkillsFleetCredentialError(`${credentialPointerEnvKey(SKILLS_APP)} names a vault item that produced an empty Skills API key \u2014 ` + `refusing to send an unauthenticated request.`);
7906
+ }
7907
+ return completed.apiKey;
7908
+ }
7909
+ async function requireSkillsApiKey(action = "This command", env = process.env, options = {}) {
7910
+ const apiKey = await resolveSkillsApiKey(env, options);
7911
+ if (!apiKey)
7912
+ throw new MissingSkillsFleetError(action);
7913
+ return apiKey;
7914
+ }
7915
+ function stripV1(baseUrl) {
7916
+ return baseUrl.replace(/\/v1$/, "").replace(/\/+$/, "");
7917
+ }
7918
+ async function skillsCredentialOrReason(env = process.env, options = {}) {
7919
+ try {
7920
+ const apiKey = await resolveSkillsApiKey(env, options);
7921
+ return apiKey ? { apiKey, reason: null } : { apiKey: null, reason: null };
7922
+ } catch (error2) {
7923
+ if (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") {
7924
+ return { apiKey: null, reason: error2.message };
7925
+ }
7926
+ throw error2;
7927
+ }
7928
+ }
7929
+ function resolveSkillsApiOrigin(env = process.env, options = {}) {
7930
+ const configured = configuredSkillsApiUrl(env, options.credentials?.keychain);
7931
+ if (configured) {
7932
+ toV1BaseUrl(configured.value);
7933
+ return { origin: normalizeSkillsApiOrigin(configured.value), source: configured.source };
7934
+ }
7935
+ const fleet = resolveSkillsFleet(env, options);
7936
+ return fleet.mode === "hosted" ? { origin: fleet.apiOrigin, source: fleet.apiUrlSource } : null;
7937
+ }
7938
+ function requireSkillsApiOrigin(action = "This command", env = process.env, options = {}) {
7939
+ const resolved = resolveSkillsApiOrigin(env, options);
7940
+ if (!resolved)
7941
+ throw new MissingSkillsFleetError(action);
7942
+ return resolved.origin;
7943
+ }
7944
+ function requireSkillsFleet(action = "This command", env = process.env, options = {}) {
7945
+ const fleet = resolveSkillsFleet(env, options);
7946
+ if (fleet.mode === "hosted")
7947
+ return fleet;
7948
+ throw new MissingSkillsFleetError(action);
7949
+ }
7950
+ var SKILLS_APP = "skills", ENV_KEYS, SKILLS_API_URL_ENV_KEYS, SKILLS_API_KEY_ENV_KEYS, SKILLS_API_URL_ENV, SKILLS_API_KEY_ENV, SkillsFleetCredentialError, localNoticePrinted = false, MissingSkillsFleetError;
7951
+ var init_fleet_credentials = __esm(() => {
7952
+ init_transport();
7953
+ ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
7954
+ SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
7955
+ SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
7956
+ SKILLS_API_URL_ENV = SKILLS_API_URL_ENV_KEYS[0];
7957
+ SKILLS_API_KEY_ENV = SKILLS_API_KEY_ENV_KEYS[0];
7958
+ SkillsFleetCredentialError = class SkillsFleetCredentialError extends Error {
7959
+ code;
7960
+ constructor(message, code = "MISSING_API_CREDENTIAL") {
7961
+ super(message);
7962
+ this.name = "SkillsFleetCredentialError";
7963
+ this.code = code;
7964
+ }
7965
+ };
7966
+ MissingSkillsFleetError = class MissingSkillsFleetError extends Error {
7967
+ code = "MISSING_API_URL";
7968
+ constructor(action = "This command") {
7969
+ super(`${action} requires a Skills API credential and none is configured \u2014 ` + `run: skills auth login, or set ${SKILLS_API_KEY_ENV} ` + `(add the Keychain item hasna.credentials.${SKILLS_APP}.api-key, or write ~/.hasna/skills/config/credentials). ` + `Point at your own instance with ${SKILLS_API_URL_ENV}, or run: skills setup --api-url <your Skills instance origin>`);
7970
+ this.name = "MissingSkillsFleetError";
7971
+ }
7972
+ };
7099
7973
  });
7100
7974
 
7101
7975
  // src/lib/blog-article.ts
@@ -7228,6 +8102,15 @@ var init_blog_article = __esm(() => {
7228
8102
  ARTICLE_LENGTHS = ["short", "medium", "long"];
7229
8103
  });
7230
8104
 
8105
+ // src/lib/auth-store.ts
8106
+ function getApiUrl(action, env = process.env, options = {}) {
8107
+ return requireSkillsApiOrigin(action, env, options);
8108
+ }
8109
+ var init_auth_store = __esm(() => {
8110
+ init_fleet_credentials();
8111
+ init_fleet_credentials();
8112
+ });
8113
+
7231
8114
  // src/lib/remote-client.ts
7232
8115
  var exports_remote_client = {};
7233
8116
  __export(exports_remote_client, {
@@ -7501,26 +8384,23 @@ function normalizeUpdatedSincePage(payload) {
7501
8384
  }
7502
8385
  return { skills, nextCursor };
7503
8386
  }
7504
- function createRemoteSkillsClient() {
7505
- const apiKey = getApiKey();
7506
- if (!apiKey)
8387
+ async function createRemoteSkillsClient(env = process.env) {
8388
+ const fleet = resolveSkillsFleet(env);
8389
+ if (fleet.mode !== "hosted")
7507
8390
  return null;
7508
- return new RemoteSkillsClient(apiKey);
7509
- }
7510
- function createRemoteSkillsClientReadOnly() {
7511
- const apiKey = getApiKeyReadOnly();
7512
- if (!apiKey)
7513
- return null;
7514
- const apiUrl = resolveApiUrl(loadConfigReadOnly(), process.env);
7515
- if (!apiUrl)
7516
- throw new MissingApiUrlError("the cloud group's sync verb (--dry-run)");
7517
- return new RemoteSkillsClient(apiKey, apiUrl);
8391
+ const apiKey = await resolveSkillsApiKey(env);
8392
+ if (!apiKey) {
8393
+ throw new Error("A Skills authority resolved but no API key did. Sign in with: skills auth login");
8394
+ }
8395
+ return new RemoteSkillsClient(apiKey, fleet.apiOrigin);
8396
+ }
8397
+ function createRemoteSkillsClientReadOnly(env = process.env) {
8398
+ return createRemoteSkillsClient(env);
7518
8399
  }
7519
8400
  var RemoteRouteUnsupportedError, RemoteRequestError;
7520
8401
  var init_remote_client = __esm(() => {
7521
8402
  init_auth_store();
7522
- init_api_url();
7523
- init_config();
8403
+ init_fleet_credentials();
7524
8404
  RemoteRouteUnsupportedError = class RemoteRouteUnsupportedError extends Error {
7525
8405
  path;
7526
8406
  status;
@@ -7651,7 +8531,7 @@ var require_content_type = __commonJS((exports) => {
7651
8531
  }
7652
8532
  });
7653
8533
 
7654
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
8534
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
7655
8535
  import process2 from "process";
7656
8536
 
7657
8537
  // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v4/core/core.js
@@ -12109,7 +12989,7 @@ function preprocess(fn, schema) {
12109
12989
  // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v4/classic/external.js
12110
12990
  config(en_default());
12111
12991
 
12112
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
12992
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
12113
12993
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
12114
12994
  var DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26";
12115
12995
  var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
@@ -12956,7 +13836,7 @@ class UrlElicitationRequiredError extends McpError {
12956
13836
  }
12957
13837
  }
12958
13838
 
12959
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
13839
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
12960
13840
  var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
12961
13841
 
12962
13842
  class ReadBuffer {
@@ -12996,7 +13876,7 @@ function serializeMessage(message) {
12996
13876
  `;
12997
13877
  }
12998
13878
 
12999
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
13879
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
13000
13880
  class StdioServerTransport {
13001
13881
  constructor(_stdin = process2.stdin, _stdout = process2.stdout, options) {
13002
13882
  this._stdin = _stdin;
@@ -13061,12 +13941,13 @@ class StdioServerTransport {
13061
13941
  // package.json
13062
13942
  var package_default = {
13063
13943
  name: "@hasna/skills",
13064
- version: "0.2.0",
13944
+ version: "0.3.0",
13065
13945
  description: "Skills library for AI coding agents",
13066
13946
  type: "module",
13067
13947
  bin: {
13068
13948
  skills: "bin/index.js",
13069
13949
  "skills-mcp": "bin/mcp.js",
13950
+ "skills-serve": "bin/server.js",
13070
13951
  "skills-server": "bin/server.js",
13071
13952
  "skills-worker": "bin/worker.js",
13072
13953
  "skills-migrate": "bin/migrate.js"
@@ -13152,6 +14033,7 @@ var package_default = {
13152
14033
  dependencies: {
13153
14034
  "@aws-sdk/client-ecs": "^3.1079.0",
13154
14035
  "@aws-sdk/client-s3": "^3.1079.0",
14036
+ "@hasna/contracts": "1.0.1",
13155
14037
  "@hasna/events": "0.1.16",
13156
14038
  "@modelcontextprotocol/sdk": "^1.26.0",
13157
14039
  chalk: "^5.3.0",
@@ -17195,7 +18077,7 @@ function object2(shape, params) {
17195
18077
  };
17196
18078
  return new ZodMiniObject(def);
17197
18079
  }
17198
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
18080
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
17199
18081
  function isZ4Schema(s) {
17200
18082
  const schema = s;
17201
18083
  return !!schema._zod;
@@ -17356,7 +18238,7 @@ function getLiteralValue(schema) {
17356
18238
  return;
17357
18239
  }
17358
18240
 
17359
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
18241
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
17360
18242
  function isTerminal(status) {
17361
18243
  return status === "completed" || status === "failed" || status === "cancelled";
17362
18244
  }
@@ -18597,7 +19479,7 @@ var zodToJsonSchema = (schema, options) => {
18597
19479
  }
18598
19480
  return combined;
18599
19481
  };
18600
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
19482
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
18601
19483
  function mapMiniTarget(t) {
18602
19484
  if (!t)
18603
19485
  return "draft-7";
@@ -18639,7 +19521,7 @@ function parseWithCompat(schema, data) {
18639
19521
  return result.data;
18640
19522
  }
18641
19523
 
18642
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
19524
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
18643
19525
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 60000;
18644
19526
 
18645
19527
  class Protocol {
@@ -19480,7 +20362,7 @@ function mergeCapabilities(base, additional) {
19480
20362
  return result;
19481
20363
  }
19482
20364
 
19483
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
20365
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
19484
20366
  var import_ajv = __toESM(require_ajv(), 1);
19485
20367
  var import_ajv_formats = __toESM(require_dist(), 1);
19486
20368
  function createDefaultAjvInstance() {
@@ -19520,7 +20402,7 @@ class AjvJsonSchemaValidator {
19520
20402
  }
19521
20403
  }
19522
20404
 
19523
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
20405
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
19524
20406
  class ExperimentalServerTasks {
19525
20407
  constructor(_server) {
19526
20408
  this._server = _server;
@@ -19598,7 +20480,7 @@ class ExperimentalServerTasks {
19598
20480
  }
19599
20481
  }
19600
20482
 
19601
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
20483
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
19602
20484
  function assertToolsCallTaskCapability(requests, method, entityName) {
19603
20485
  if (!requests) {
19604
20486
  throw new Error(`${entityName} does not support task creation (required for ${method})`);
@@ -19633,7 +20515,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
19633
20515
  }
19634
20516
  }
19635
20517
 
19636
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
20518
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
19637
20519
  class Server extends Protocol {
19638
20520
  constructor(_serverInfo, options) {
19639
20521
  super(options);
@@ -19957,7 +20839,7 @@ class Server extends Protocol {
19957
20839
  }
19958
20840
  }
19959
20841
 
19960
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
20842
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
19961
20843
  var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
19962
20844
  function isCompletable(schema) {
19963
20845
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -19971,7 +20853,7 @@ var McpZodTypeKind;
19971
20853
  McpZodTypeKind2["Completable"] = "McpCompletable";
19972
20854
  })(McpZodTypeKind || (McpZodTypeKind = {}));
19973
20855
 
19974
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
20856
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
19975
20857
  var MAX_TEMPLATE_LENGTH = 1e6;
19976
20858
  var MAX_VARIABLE_LENGTH = 1e6;
19977
20859
  var MAX_TEMPLATE_EXPRESSIONS = 1e4;
@@ -20189,7 +21071,7 @@ class UriTemplate {
20189
21071
  }
20190
21072
  }
20191
21073
 
20192
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
21074
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
20193
21075
  var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
20194
21076
  function validateToolName(name) {
20195
21077
  const warnings = [];
@@ -20247,7 +21129,7 @@ function validateAndWarnToolName(name) {
20247
21129
  return result.isValid;
20248
21130
  }
20249
21131
 
20250
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
21132
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
20251
21133
  class ExperimentalMcpServerTasks {
20252
21134
  constructor(_mcpServer) {
20253
21135
  this._mcpServer = _mcpServer;
@@ -20261,7 +21143,7 @@ class ExperimentalMcpServerTasks {
20261
21143
  return mcpServerInternal._createRegisteredTool(name, config2.title, config2.description, config2.inputSchema, config2.outputSchema, config2.annotations, execution, config2._meta, handler);
20262
21144
  }
20263
21145
  }
20264
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
21146
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
20265
21147
  class McpServer {
20266
21148
  constructor(serverInfo, options) {
20267
21149
  this._registeredResources = {};
@@ -21004,12 +21886,270 @@ var EMPTY_COMPLETION_RESULT = {
21004
21886
  };
21005
21887
 
21006
21888
  // src/lib/registry.ts
21007
- init_config();
21008
21889
  import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync6 } from "fs";
21009
21890
  import { join as join8 } from "path";
21010
21891
 
21892
+ // src/lib/config.ts
21893
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
21894
+ import { join as join2, dirname } from "path";
21895
+
21896
+ // src/lib/retired-settings.ts
21897
+ var RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
21898
+ var RETIRED_CONFIG_KEYS = {
21899
+ mode: "a configured API origin",
21900
+ apiUrl: "skills setup --api-url <origin>"
21901
+ };
21902
+ var RETIRED_CONFIG_KEY_REASONS = {
21903
+ mode: "Deployment modes were removed: a Skills client either resolves a credential " + "or it does not, and that is the whole of it.",
21904
+ apiUrl: "The service address is no longer kept in this app's config file: it is read from " + "HASNA_SKILLS_API_URL, then the macOS Keychain item hasna.credentials.skills.api-url, " + "then ~/.hasna/skills/config/credentials, then the fleet gateway."
21905
+ };
21906
+
21907
+ class RetiredSettingError extends Error {
21908
+ code = "RETIRED_SETTING";
21909
+ setting;
21910
+ constructor(setting, message) {
21911
+ super(message);
21912
+ this.name = "RetiredSettingError";
21913
+ this.setting = setting;
21914
+ }
21915
+ }
21916
+ function isRetiredModeEnvVar(name, app) {
21917
+ const upper = name.toUpperCase();
21918
+ if (!upper.includes(app.toUpperCase()))
21919
+ return false;
21920
+ return RETIRED_ENV_SUFFIXES.some((suffix) => upper.endsWith(suffix));
21921
+ }
21922
+ function findRetiredModeEnvVars(env, app) {
21923
+ const found = [];
21924
+ for (const [name, value] of Object.entries(env)) {
21925
+ if (value === undefined || value === "")
21926
+ continue;
21927
+ if (isRetiredModeEnvVar(name, app))
21928
+ found.push({ name, value });
21929
+ }
21930
+ return found.sort((a, b) => a.name.localeCompare(b.name));
21931
+ }
21932
+ function assertNoRetiredModeEnvVars(env, options) {
21933
+ const found = findRetiredModeEnvVars(env, options.app);
21934
+ if (found.length === 0)
21935
+ return;
21936
+ const names = found.map((entry) => entry.name);
21937
+ throw new RetiredSettingError(names[0], `${names.join(", ")} ${names.length === 1 ? "is" : "are"} no longer read. ` + "Deployment modes were removed: where a server keeps its data is decided by the " + `database it is given, not by a declared label. Set ${options.replacement} to a ` + "postgres:// URL to use PostgreSQL, or leave it unset for the on-box SQLite database. " + `Then unset ${names.join(" and ")}. ` + "Refused rather than ignored, because a discarded setting looks exactly like a " + "working one until something needs the data.");
21938
+ }
21939
+ function assertNoRetiredConfigKeys(config2, source) {
21940
+ for (const [key, replacement] of Object.entries(RETIRED_CONFIG_KEYS)) {
21941
+ if (!(key in config2))
21942
+ continue;
21943
+ throw new RetiredSettingError(key, `${source}: "${key}" is no longer a configuration key. ` + `${RETIRED_CONFIG_KEY_REASONS[key] ?? ""} ` + `Use ${replacement} instead, and remove the old key with: skills config unset ${key}. ` + "Refused rather than ignored, because silently dropping it would leave an " + "operator believing they had pointed this install at a server.");
21944
+ }
21945
+ }
21946
+
21947
+ // src/lib/app-home.ts
21948
+ import { existsSync } from "fs";
21949
+ import { homedir } from "os";
21950
+ import { join, resolve } from "path";
21951
+ import { homedir as pathsResolverHomedir } from "os";
21952
+ import { join as pathsResolverJoin } from "path";
21953
+ var PATHS_RESOLVER_KIND_ENV = {
21954
+ config: "HASNA_CONFIG_HOME",
21955
+ data: "HASNA_DATA_HOME",
21956
+ state: "HASNA_STATE_HOME",
21957
+ cache: "HASNA_CACHE_HOME"
21958
+ };
21959
+ var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
21960
+ function pathsResolverAssertApp(app) {
21961
+ if (typeof app !== "string" || app.length === 0) {
21962
+ throw new TypeError("paths: app must be a non-empty string");
21963
+ }
21964
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
21965
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
21966
+ }
21967
+ }
21968
+ function pathsResolverAssertKind(kind) {
21969
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
21970
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
21971
+ }
21972
+ }
21973
+ function pathsResolverBaseDir(kind, options) {
21974
+ pathsResolverAssertKind(kind);
21975
+ const env = options.env ?? process.env;
21976
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
21977
+ if (typeof override === "string" && override.length > 0)
21978
+ return override;
21979
+ const home = options.home ?? pathsResolverHomedir();
21980
+ const platform = options.platform ?? process.platform;
21981
+ if (platform === "darwin") {
21982
+ switch (kind) {
21983
+ case "config":
21984
+ case "data":
21985
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
21986
+ case "cache":
21987
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
21988
+ case "state":
21989
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
21990
+ }
21991
+ }
21992
+ switch (kind) {
21993
+ case "config":
21994
+ return pathsResolverJoin(home, ".config", "hasna");
21995
+ case "data":
21996
+ return pathsResolverJoin(home, ".local", "share", "hasna");
21997
+ case "state":
21998
+ return pathsResolverJoin(home, ".local", "state", "hasna");
21999
+ case "cache":
22000
+ return pathsResolverJoin(home, ".cache", "hasna");
22001
+ }
22002
+ }
22003
+ function pathsResolverResolve(kind, options) {
22004
+ pathsResolverAssertApp(options.app);
22005
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
22006
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
22007
+ }
22008
+ function dataDir(options) {
22009
+ return pathsResolverResolve("data", options);
22010
+ }
22011
+ var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
22012
+ var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
22013
+ var SKILLS_HOME_ENV = "SKILLS_HOME";
22014
+ var DEFAULT_SQLITE_FILENAME = "server.db";
22015
+ var GLOBAL_CONFIG_FILENAME = "config.json";
22016
+ function effectiveHome() {
22017
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
22018
+ }
22019
+ function legacyDataRoot() {
22020
+ return join(effectiveHome(), ".hasna", "skills");
22021
+ }
22022
+ function resolverDataRoot(home = effectiveHome(), env) {
22023
+ return dataDir({ app: "skills", home, env });
22024
+ }
22025
+ function adoptResolverDataRoot(resolved, env = process.env) {
22026
+ const dataOverride = env.HASNA_DATA_HOME;
22027
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
22028
+ return true;
22029
+ return existsSync(join(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join(resolved, GLOBAL_CONFIG_FILENAME));
22030
+ }
22031
+ function exactDataRoot() {
22032
+ for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
22033
+ const dir = process.env[key]?.trim();
22034
+ if (dir)
22035
+ return resolve(dir);
22036
+ }
22037
+ return;
22038
+ }
22039
+ function hasExactOverride(env = process.env) {
22040
+ return Boolean(env[DATA_DIR_ENV]?.trim()) || Boolean(env[HASNA_SKILLS_HOME_ENV]?.trim()) || Boolean(env[SKILLS_HOME_ENV]?.trim());
22041
+ }
22042
+ function hasOperatorOverride(env = process.env) {
22043
+ return hasExactOverride(env) || Boolean(env.HASNA_DATA_HOME?.trim());
22044
+ }
22045
+ function getDataRoot() {
22046
+ const exact = exactDataRoot();
22047
+ if (exact)
22048
+ return exact;
22049
+ const resolved = resolverDataRoot();
22050
+ return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
22051
+ }
22052
+
22053
+ // src/lib/config.ts
22054
+ var ENUM_KEYS = {
22055
+ defaultAgent: ["claude", "codex", "gemini", "pi", "opencode", "all"],
22056
+ defaultScope: ["global", "project"],
22057
+ format: ["compact", "json", "csv"]
22058
+ };
22059
+ var STRING_KEYS = ["extensionsDir"];
22060
+ function validKeys() {
22061
+ return [...Object.keys(ENUM_KEYS), ...STRING_KEYS];
22062
+ }
22063
+ function allowedValues(key) {
22064
+ return ENUM_KEYS[key];
22065
+ }
22066
+ function mergeDirectoryContents(sourceDir, targetDir) {
22067
+ if (!existsSync2(sourceDir))
22068
+ return;
22069
+ mkdirSync(targetDir, { recursive: true });
22070
+ for (const entry of readdirSync(sourceDir)) {
22071
+ const sourcePath = join2(sourceDir, entry);
22072
+ const targetPath = join2(targetDir, entry);
22073
+ try {
22074
+ const sourceStat = statSync(sourcePath);
22075
+ if (sourceStat.isDirectory()) {
22076
+ mergeDirectoryContents(sourcePath, targetPath);
22077
+ continue;
22078
+ }
22079
+ if (!existsSync2(targetPath))
22080
+ copyFileSync(sourcePath, targetPath);
22081
+ } catch {}
22082
+ }
22083
+ }
22084
+ function normalizeConfigValue(key, value) {
22085
+ if (typeof value !== "string")
22086
+ return;
22087
+ const allowed = allowedValues(key);
22088
+ if (allowed)
22089
+ return allowed.includes(value) ? value : undefined;
22090
+ if (key === "extensionsDir")
22091
+ return value.trim() ? value : undefined;
22092
+ return;
22093
+ }
22094
+ var INSTALLED_SKILLS_DIRNAME = "installed";
22095
+ var SKILLS_CACHE_DIRNAME = "skills";
22096
+ var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
22097
+ function isOwnerLayoutMigrated(appDir) {
22098
+ return existsSync2(join2(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
22099
+ }
22100
+ function getDataDir() {
22101
+ const root = getDataRoot();
22102
+ try {
22103
+ mkdirSync(root, { recursive: true });
22104
+ } catch {}
22105
+ if (hasOperatorOverride())
22106
+ return root;
22107
+ const home = effectiveHome();
22108
+ const oldDir = join2(home, ".skills");
22109
+ const oldConfigFile = join2(home, ".skillsrc");
22110
+ try {
22111
+ mergeDirectoryContents(oldDir, root);
22112
+ } catch {}
22113
+ if (existsSync2(oldConfigFile) && !existsSync2(join2(root, "config.json"))) {
22114
+ try {
22115
+ copyFileSync(oldConfigFile, join2(root, "config.json"));
22116
+ } catch {}
22117
+ }
22118
+ return root;
22119
+ }
22120
+ function getConfigPath(scope) {
22121
+ if (scope === "global") {
22122
+ return join2(getDataDir(), "config.json");
22123
+ }
22124
+ return join2(process.cwd(), "skills.config.json");
22125
+ }
22126
+ function readConfigFile(path) {
22127
+ if (!existsSync2(path))
22128
+ return {};
22129
+ let parsed;
22130
+ try {
22131
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
22132
+ } catch {
22133
+ return {};
22134
+ }
22135
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
22136
+ return {};
22137
+ assertNoRetiredConfigKeys(parsed, path);
22138
+ const config2 = {};
22139
+ for (const key of validKeys()) {
22140
+ const value = normalizeConfigValue(key, parsed[key]);
22141
+ if (value !== undefined)
22142
+ config2[key] = value;
22143
+ }
22144
+ return config2;
22145
+ }
22146
+ function loadConfig() {
22147
+ const globalConfig2 = readConfigFile(getConfigPath("global"));
22148
+ const projectConfig = readConfigFile(getConfigPath("project"));
22149
+ return { ...globalConfig2, ...projectConfig };
22150
+ }
22151
+
21011
22152
  // src/lib/portable-skills.ts
21012
- init_config();
21013
22153
  import {
21014
22154
  cpSync as cpSync2,
21015
22155
  existsSync as existsSync7,
@@ -22445,7 +23585,7 @@ function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)
22445
23585
  const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
22446
23586
  const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
22447
23587
  const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
22448
- const version2 = stringField(jsonManifest, "version") ?? frontmatter?.version ?? stringValue(pkg?.version) ?? PORTABLE_SKILL_DEFAULT_VERSION;
23588
+ const version2 = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
22449
23589
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
22450
23590
  const commands = parseManifestCommands(jsonManifest) ?? (kind === "instruction" ? [] : inferPackageCommands(pkg, name)) ?? [];
22451
23591
  return {
@@ -22469,6 +23609,15 @@ function parseSkillKind(value) {
22469
23609
  return value;
22470
23610
  return;
22471
23611
  }
23612
+ function readDeclaredSkillVersion(skillPath) {
23613
+ const skillJsonPath = join6(skillPath, "skill.json");
23614
+ const skillMdPath = join6(skillPath, "SKILL.md");
23615
+ const pkgPath = join6(skillPath, "package.json");
23616
+ const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
23617
+ const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
23618
+ const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
23619
+ return stringField(jsonManifest, "version") ?? frontmatter?.version ?? stringValue(pkg?.version);
23620
+ }
22472
23621
  function createInstructionManifest(name, options) {
22473
23622
  return {
22474
23623
  $schema: PORTABLE_SKILL_SCHEMA,
@@ -23591,21 +24740,13 @@ function mergeCustomSkills(skills) {
23591
24740
  // src/lib/installer.ts
23592
24741
  import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync as rmSync2 } from "fs";
23593
24742
  import { dirname as dirname4, join as join10 } from "path";
23594
- import { homedir as homedir3 } from "os";
24743
+ import { homedir as homedir2 } from "os";
23595
24744
  import { fileURLToPath } from "url";
23596
-
23597
- // src/lib/home-migration.ts
23598
- init_config();
23599
- init_config();
23600
-
23601
24745
  // src/lib/utils.ts
23602
24746
  function normalizeSkillName(name) {
23603
24747
  return name;
23604
24748
  }
23605
24749
 
23606
- // src/lib/installer.ts
23607
- init_config();
23608
-
23609
24750
  // src/lib/project-state.ts
23610
24751
  import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
23611
24752
  import { join as join9 } from "path";
@@ -23815,11 +24956,11 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
23815
24956
  const base = projectDir || process.cwd();
23816
24957
  switch (agent) {
23817
24958
  case "pi":
23818
- return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir3(), ".pi", "agent", "skills");
24959
+ return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir2(), ".pi", "agent", "skills");
23819
24960
  case "opencode":
23820
- return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir3(), ".config", "opencode", "skills");
24961
+ return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir2(), ".config", "opencode", "skills");
23821
24962
  default:
23822
- return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir3(), `.${agent}`, "skills");
24963
+ return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir2(), `.${agent}`, "skills");
23823
24964
  }
23824
24965
  }
23825
24966
  function warnMissingDependencies(name, targetDir) {
@@ -23914,7 +25055,8 @@ function getSkillRequirements(name) {
23914
25055
  }
23915
25056
  }
23916
25057
  envVars.delete("SKILL_API_KEY");
23917
- envVars.add("SKILLS_API_KEY");
25058
+ envVars.delete("SKILLS_API_KEY");
25059
+ envVars.add("HASNA_SKILLS_API_KEY");
23918
25060
  }
23919
25061
  const systemDeps = new Set;
23920
25062
  const depPatterns = [
@@ -25049,7 +26191,7 @@ var TOOL_PRIMITIVES = [
25049
26191
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
25050
26192
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
25051
26193
  apiSurfaces: ["runSkill", "SkillRunRecord", "RemoteSkillRunContract"],
25052
- envVars: ["SKILLS_API_KEY"],
26194
+ envVars: ["HASNA_SKILLS_API_KEY"],
25053
26195
  outputTypes: ["text", "json", "markdown", "artifact"],
25054
26196
  capabilities: ["completion", "reasoning", "tool-calling", "vision-input", "structured-output"]
25055
26197
  },
@@ -25105,7 +26247,7 @@ var TOOL_PRIMITIVES = [
25105
26247
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
25106
26248
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
25107
26249
  apiSurfaces: ["runSkill", "RemoteSkillRunContract"],
25108
- envVars: ["SKILLS_API_KEY"],
26250
+ envVars: ["HASNA_SKILLS_API_KEY"],
25109
26251
  outputTypes: ["png", "jpeg", "webp", "svg", "zip"],
25110
26252
  capabilities: ["image-generation", "image-analysis", "image-editing", "asset-packaging"]
25111
26253
  },
@@ -25119,7 +26261,7 @@ var TOOL_PRIMITIVES = [
25119
26261
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
25120
26262
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
25121
26263
  apiSurfaces: ["runSkill", "RemoteSkillRunContract"],
25122
- envVars: ["SKILLS_API_KEY"],
26264
+ envVars: ["HASNA_SKILLS_API_KEY"],
25123
26265
  outputTypes: ["mp3", "wav", "txt", "srt", "json", "zip"],
25124
26266
  capabilities: ["transcription", "audio-generation", "voiceover", "audio-cleanup"]
25125
26267
  },
@@ -25133,7 +26275,7 @@ var TOOL_PRIMITIVES = [
25133
26275
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
25134
26276
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
25135
26277
  apiSurfaces: ["runSkill", "RemoteSkillRunContract"],
25136
- envVars: ["SKILLS_API_KEY"],
26278
+ envVars: ["HASNA_SKILLS_API_KEY"],
25137
26279
  outputTypes: ["mp4", "mov", "srt", "png", "json", "zip"],
25138
26280
  capabilities: ["video-generation", "video-analysis", "captioning", "highlight-extraction"]
25139
26281
  },
@@ -25161,7 +26303,7 @@ var TOOL_PRIMITIVES = [
25161
26303
  cliCommands: ["skills tools deps <skill>", "skills run <skill>"],
25162
26304
  mcpTools: ["get_skill_tool_dependencies", "run_skill"],
25163
26305
  apiSurfaces: ["SkillRunContext", "RemoteSkillRunContract"],
25164
- envVars: ["SKILLS_API_KEY"],
26306
+ envVars: ["HASNA_SKILLS_API_KEY"],
25165
26307
  outputTypes: ["json", "artifact"],
25166
26308
  capabilities: ["approval", "external-api", "account-scoped-execution"]
25167
26309
  },
@@ -25203,7 +26345,7 @@ var TOOL_PRIMITIVES = [
25203
26345
  cliCommands: ["skills auth login", "skills run <skill>"],
25204
26346
  mcpTools: ["run_skill"],
25205
26347
  apiSurfaces: ["RemoteSkillsClient", "RemoteSkillRunContract"],
25206
- envVars: ["SKILLS_API_KEY"],
26348
+ envVars: ["HASNA_SKILLS_API_KEY"],
25207
26349
  outputTypes: ["json"],
25208
26350
  capabilities: ["account-auth", "remote-run-submit"]
25209
26351
  }
@@ -25699,7 +26841,7 @@ function registerDiscoveryTools(server) {
25699
26841
  }
25700
26842
 
25701
26843
  // src/mcp/operation-tools.ts
25702
- import { existsSync as existsSync14, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
26844
+ import { existsSync as existsSync13, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
25703
26845
  import { join as join14 } from "path";
25704
26846
 
25705
26847
  // src/lib/run-state.ts
@@ -25882,8 +27024,7 @@ function mimeForPath(path) {
25882
27024
  }
25883
27025
  }
25884
27026
  // src/lib/run-routing.ts
25885
- init_api_url();
25886
- init_auth_store();
27027
+ init_fleet_credentials();
25887
27028
  function isServerOwnedSkill(skill) {
25888
27029
  return skill.serverOwned === true;
25889
27030
  }
@@ -25894,7 +27035,7 @@ function resolveRunRouting(skill, apiKey, apiUrl) {
25894
27035
  return {
25895
27036
  route: "error",
25896
27037
  code: "REMOTE_REQUIRES_ORIGIN",
25897
- error: `${skill.name} is a server-owned skill. Point the CLI at a Skills API: ` + `skills setup --api-url <url> (or export SKILLS_API_URL)`
27038
+ error: `${skill.name} is a server-owned skill. Point the CLI at a Skills API: ` + `skills setup --api-url <url> (or export HASNA_SKILLS_API_URL)`
25898
27039
  };
25899
27040
  }
25900
27041
  if (!apiKey) {
@@ -25906,8 +27047,23 @@ function resolveRunRouting(skill, apiKey, apiUrl) {
25906
27047
  }
25907
27048
  return { route: "remote", apiKey };
25908
27049
  }
25909
- function resolveConfiguredRunRouting(skill) {
25910
- return resolveRunRouting(skill, getApiKey(), resolveApiUrl());
27050
+ async function resolveConfiguredRunRouting(skill, env = process.env) {
27051
+ let fleet;
27052
+ let apiKey;
27053
+ try {
27054
+ fleet = resolveSkillsFleet(env);
27055
+ apiKey = fleet.mode === "hosted" ? await resolveSkillsApiKey(env) : null;
27056
+ } catch (error2) {
27057
+ const isMissingCredential = (error2 instanceof SkillsFleetCredentialError || error2?.name === "SkillsFleetCredentialError") && error2.code === "MISSING_API_CREDENTIAL";
27058
+ if (!isMissingCredential)
27059
+ throw error2;
27060
+ return {
27061
+ route: "error",
27062
+ code: "REMOTE_REQUIRES_CREDENTIAL",
27063
+ error: `${skill.name} is a server-owned skill. ${error2.message}`
27064
+ };
27065
+ }
27066
+ return resolveRunRouting(skill, apiKey, fleet.apiOrigin ?? undefined);
25911
27067
  }
25912
27068
 
25913
27069
  // src/mcp/operation-tools.ts
@@ -26131,7 +27287,7 @@ function registerOperationTools(server) {
26131
27287
  return mcpError("INVALID_BLOG_ARTICLE_OPTIONS", validation.errors.join(" "));
26132
27288
  }
26133
27289
  }
26134
- const routing = resolveConfiguredRunRouting(skill);
27290
+ const routing = await resolveConfiguredRunRouting(skill);
26135
27291
  const runContext = createSkillRun({
26136
27292
  skill: skillName,
26137
27293
  args: runArgs,
@@ -26210,10 +27366,10 @@ function registerOperationTools(server) {
26210
27366
  detail: exports_external.boolean().optional()
26211
27367
  }
26212
27368
  }, async ({ run_id, detail }) => {
26213
- const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_auth_store(), exports_auth_store));
26214
- const apiKey = getApiKey2();
27369
+ const { skillsCredentialOrReason: skillsCredentialOrReason2 } = await Promise.resolve().then(() => (init_fleet_credentials(), exports_fleet_credentials));
27370
+ const { apiKey, reason } = await skillsCredentialOrReason2();
26215
27371
  if (!apiKey) {
26216
- return mcpError("AUTH_REQUIRED", "Remote run status requires API access. Run: skills auth login", ["skills auth login"]);
27372
+ return mcpError("AUTH_REQUIRED", reason ?? "Remote run status requires API access. Run: skills auth login", ["skills auth login"]);
26217
27373
  }
26218
27374
  const localRun = findSkillRun(run_id);
26219
27375
  const remoteRunId = localRun?.remoteRunId || run_id;
@@ -26307,7 +27463,7 @@ function registerOperationTools(server) {
26307
27463
  const agents = [];
26308
27464
  for (const agent of AGENT_TARGETS) {
26309
27465
  const agentSkillsPath = getAgentSkillsDir(agent, "global");
26310
- const exists = existsSync14(agentSkillsPath);
27466
+ const exists = existsSync13(agentSkillsPath);
26311
27467
  let skillCount = 0;
26312
27468
  if (exists) {
26313
27469
  try {
@@ -26358,19 +27514,29 @@ function compactRunToolPayload(payload, detailHint) {
26358
27514
  }
26359
27515
 
26360
27516
  // src/lib/feedback.ts
26361
- init_api_url();
26362
- init_config();
26363
- import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
26364
- import { dirname as dirname6, join as join15 } from "path";
27517
+ import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync6 } from "fs";
27518
+ import { dirname as dirname5, join as join15 } from "path";
26365
27519
  import { Database } from "bun:sqlite";
27520
+
27521
+ // src/lib/api-url.ts
27522
+ init_fleet_credentials();
27523
+ init_fleet_credentials();
27524
+ var API_URL_ENV_VAR = SKILLS_API_URL_ENV;
27525
+ var MISSING_API_URL_HINT = `run: skills auth login, or set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
27526
+ function resolveApiUrl(env = process.env, options = {}) {
27527
+ const fleet = resolveSkillsFleet(env, options);
27528
+ return fleet.mode === "hosted" ? fleet.apiOrigin : undefined;
27529
+ }
27530
+
27531
+ // src/lib/feedback.ts
26366
27532
  function getFeedbackDbPath() {
26367
27533
  return join15(getDataDir(), "skills.db");
26368
27534
  }
26369
27535
  function getFeedbackDb() {
26370
27536
  const dbPath = getFeedbackDbPath();
26371
- const dir = dirname6(dbPath);
26372
- if (!existsSync15(dir))
26373
- mkdirSync7(dir, { recursive: true });
27537
+ const dir = dirname5(dbPath);
27538
+ if (!existsSync14(dir))
27539
+ mkdirSync6(dir, { recursive: true });
26374
27540
  const db = new Database(dbPath);
26375
27541
  db.exec("PRAGMA journal_mode = WAL");
26376
27542
  db.exec([
@@ -26397,9 +27563,9 @@ function saveFeedback(input) {
26397
27563
  const category = input.category ?? "general";
26398
27564
  if (isApiMode()) {
26399
27565
  const path = join15(getDataDir(), "feedback.jsonl");
26400
- const dir = dirname6(path);
26401
- if (!existsSync15(dir))
26402
- mkdirSync7(dir, { recursive: true });
27566
+ const dir = dirname5(path);
27567
+ if (!existsSync14(dir))
27568
+ mkdirSync6(dir, { recursive: true });
26403
27569
  appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
26404
27570
  `);
26405
27571
  return { saved: true, category, path };
@@ -26413,12 +27579,10 @@ function saveFeedback(input) {
26413
27579
  return { saved: true, category, path: getFeedbackDbPath() };
26414
27580
  }
26415
27581
  function isApiMode(env = process.env) {
26416
- if (env.HASNA_SKILLS_API_URL?.trim())
26417
- return true;
26418
27582
  try {
26419
- return Boolean(resolveApiUrl(undefined, env));
27583
+ return Boolean(resolveApiUrl(env));
26420
27584
  } catch {
26421
- return Boolean(env.SKILLS_API_URL?.trim());
27585
+ return true;
26422
27586
  }
26423
27587
  }
26424
27588
 
@@ -26546,14 +27710,14 @@ function registerResourceMetaTools(server) {
26546
27710
  }
26547
27711
 
26548
27712
  // src/lib/scheduler.ts
26549
- import { existsSync as existsSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
27713
+ import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
26550
27714
  import { join as join16 } from "path";
26551
27715
  function getSchedulesPath(targetDir = process.cwd()) {
26552
27716
  return join16(targetDir, ".skills", "schedules.json");
26553
27717
  }
26554
27718
  function loadSchedules(targetDir = process.cwd()) {
26555
27719
  const path = getSchedulesPath(targetDir);
26556
- if (existsSync16(path)) {
27720
+ if (existsSync15(path)) {
26557
27721
  try {
26558
27722
  return JSON.parse(readFileSync12(path, "utf-8"));
26559
27723
  } catch {}
@@ -26563,9 +27727,9 @@ function loadSchedules(targetDir = process.cwd()) {
26563
27727
  function saveSchedules(data, targetDir = process.cwd()) {
26564
27728
  const path = getSchedulesPath(targetDir);
26565
27729
  const dir = join16(targetDir, ".skills");
26566
- if (!existsSync16(dir))
26567
- mkdirSync8(dir, { recursive: true });
26568
- writeFileSync7(path, JSON.stringify(data, null, 2));
27730
+ if (!existsSync15(dir))
27731
+ mkdirSync7(dir, { recursive: true });
27732
+ writeFileSync6(path, JSON.stringify(data, null, 2));
26569
27733
  }
26570
27734
  function validateCronField(expr, min, max, label) {
26571
27735
  for (const part of expr.split(",")) {
@@ -26823,18 +27987,16 @@ function registerScheduleTools(server) {
26823
27987
  }
26824
27988
 
26825
27989
  // src/lib/native-storage.ts
26826
- init_config();
26827
27990
  import { createHash as createHash3, createHmac } from "crypto";
26828
27991
  import {
26829
- existsSync as existsSync17,
26830
- mkdirSync as mkdirSync9,
27992
+ existsSync as existsSync16,
27993
+ mkdirSync as mkdirSync8,
26831
27994
  readFileSync as readFileSync13,
26832
27995
  readdirSync as readdirSync9,
26833
27996
  statSync as statSync9,
26834
- writeFileSync as writeFileSync8
27997
+ writeFileSync as writeFileSync7
26835
27998
  } from "fs";
26836
- import { dirname as dirname7, join as join17, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
26837
- init_retired_settings();
27999
+ import { dirname as dirname6, join as join17, normalize as normalize3, relative as relative3, sep as sep2 } from "path";
26838
28000
  var SKILLS_STORAGE_TABLES = [
26839
28001
  "skills_sync_records",
26840
28002
  "skills_sync_cursors"
@@ -26930,7 +28092,7 @@ function getStorageStatus(options = {}) {
26930
28092
  function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
26931
28093
  const projectStateDir = getProjectStateDir(targetDir);
26932
28094
  const files = [];
26933
- if (existsSync17(projectStateDir)) {
28095
+ if (existsSync16(projectStateDir)) {
26934
28096
  for (const filePath of walkFiles2(projectStateDir)) {
26935
28097
  const bytes = readFileSync13(filePath);
26936
28098
  const relativePath = toPosix(relative3(targetDir, filePath));
@@ -27685,7 +28847,7 @@ var getRequestListener = (fetchCallback, options = {}) => {
27685
28847
  };
27686
28848
  };
27687
28849
 
27688
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/mediaType.js
28850
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/mediaType.js
27689
28851
  var import_content_type = __toESM(require_content_type(), 1);
27690
28852
  function mediaTypeEssence(header) {
27691
28853
  if (!header) {
@@ -27708,7 +28870,7 @@ function isJsonContentType(header) {
27708
28870
  return mediaTypeEssence(header) === "application/json";
27709
28871
  }
27710
28872
 
27711
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/sseKeepAlive.js
28873
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/sseKeepAlive.js
27712
28874
  var DEFAULT_SSE_KEEP_ALIVE_MS = 15000;
27713
28875
  var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
27714
28876
  function armSseKeepAlive(intervalMs, onTick) {
@@ -27720,7 +28882,7 @@ function armSseKeepAlive(intervalMs, onTick) {
27720
28882
  return timer;
27721
28883
  }
27722
28884
 
27723
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
28885
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
27724
28886
  class WebStandardStreamableHTTPServerTransport {
27725
28887
  constructor(options = {}) {
27726
28888
  this._started = false;
@@ -28377,7 +29539,7 @@ data:
28377
29539
  }
28378
29540
  }
28379
29541
 
28380
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
29542
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.0+2b91fc17bf64bdfd/node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
28381
29543
  class StreamableHTTPServerTransport {
28382
29544
  constructor(options = {}) {
28383
29545
  this._requestContext = new WeakMap;