@kinosec/cli 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +1346 -428
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +3 -3
package/dist/cli.js CHANGED
@@ -5383,25 +5383,44 @@ var {
5383
5383
  // src/lib/errors.ts
5384
5384
  var import_picocolors2 = __toESM(require_picocolors(), 1);
5385
5385
 
5386
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/errors/HTTPError.js
5387
- var HTTPError = class extends Error {
5386
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/KyError.js
5387
+ var KyError = class extends Error {
5388
+ name = "KyError";
5389
+ get isKyError() {
5390
+ return true;
5391
+ }
5392
+ };
5393
+
5394
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/HTTPError.js
5395
+ var HTTPError = class extends KyError {
5396
+ name = "HTTPError";
5388
5397
  response;
5389
5398
  request;
5390
5399
  options;
5400
+ data;
5391
5401
  constructor(response, request, options) {
5392
5402
  const code = response.status || response.status === 0 ? response.status : "";
5393
5403
  const title = response.statusText ?? "";
5394
5404
  const status = `${code} ${title}`.trim();
5395
5405
  const reason = status ? `status code ${status}` : "an unknown error";
5396
5406
  super(`Request failed with ${reason}: ${request.method} ${request.url}`);
5397
- this.name = "HTTPError";
5398
5407
  this.response = response;
5399
5408
  this.request = request;
5400
5409
  this.options = options;
5401
5410
  }
5402
5411
  };
5403
5412
 
5404
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/errors/NonError.js
5413
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/NetworkError.js
5414
+ var NetworkError = class extends KyError {
5415
+ name = "NetworkError";
5416
+ request;
5417
+ constructor(request, options) {
5418
+ super(`Request failed due to a network error: ${request.method} ${request.url}`, options);
5419
+ this.request = request;
5420
+ }
5421
+ };
5422
+
5423
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/NonError.js
5405
5424
  var NonError = class extends Error {
5406
5425
  name = "NonError";
5407
5426
  value;
@@ -5420,8 +5439,8 @@ var NonError = class extends Error {
5420
5439
  }
5421
5440
  };
5422
5441
 
5423
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/errors/ForceRetryError.js
5424
- var ForceRetryError = class extends Error {
5442
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/ForceRetryError.js
5443
+ var ForceRetryError = class extends KyError {
5425
5444
  name = "ForceRetryError";
5426
5445
  customDelay;
5427
5446
  code;
@@ -5435,7 +5454,27 @@ var ForceRetryError = class extends Error {
5435
5454
  }
5436
5455
  };
5437
5456
 
5438
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/core/constants.js
5457
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/SchemaValidationError.js
5458
+ var SchemaValidationError = class extends Error {
5459
+ name = "SchemaValidationError";
5460
+ issues;
5461
+ constructor(issues) {
5462
+ super("Response schema validation failed");
5463
+ this.issues = issues;
5464
+ }
5465
+ };
5466
+
5467
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/TimeoutError.js
5468
+ var TimeoutError = class extends KyError {
5469
+ name = "TimeoutError";
5470
+ request;
5471
+ constructor(request) {
5472
+ super(`Request timed out: ${request.method} ${request.url}`);
5473
+ this.request = request;
5474
+ }
5475
+ };
5476
+
5477
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/core/constants.js
5439
5478
  var supportsRequestStreams = (() => {
5440
5479
  let duplexAccessed = false;
5441
5480
  let hasContentType = false;
@@ -5479,7 +5518,7 @@ var responseTypes = {
5479
5518
  bytes: "*/*"
5480
5519
  };
5481
5520
  var maxSafeTimeout = 2147483647;
5482
- var usualFormBoundarySize = new TextEncoder().encode("------WebKitFormBoundaryaxpyiPgbbPti10Rw").length;
5521
+ var usualFormBoundarySize = 40;
5483
5522
  var stop = /* @__PURE__ */ Symbol("stop");
5484
5523
  var RetryMarker = class {
5485
5524
  options;
@@ -5493,9 +5532,11 @@ var kyOptionKeys = {
5493
5532
  parseJson: true,
5494
5533
  stringifyJson: true,
5495
5534
  searchParams: true,
5496
- prefixUrl: true,
5535
+ baseUrl: true,
5536
+ prefix: true,
5497
5537
  retry: true,
5498
5538
  timeout: true,
5539
+ totalTimeout: true,
5499
5540
  hooks: true,
5500
5541
  throwHttpErrors: true,
5501
5542
  onDownloadProgress: true,
@@ -5503,10 +5544,6 @@ var kyOptionKeys = {
5503
5544
  fetch: true,
5504
5545
  context: true
5505
5546
  };
5506
- var vendorSpecificOptions = {
5507
- next: true
5508
- // Next.js cache revalidation (revalidate, tags)
5509
- };
5510
5547
  var requestOptionsRegistry = {
5511
5548
  method: true,
5512
5549
  headers: true,
@@ -5524,7 +5561,8 @@ var requestOptionsRegistry = {
5524
5561
  duplex: true
5525
5562
  };
5526
5563
 
5527
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/body.js
5564
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/body.js
5565
+ var encoder = new TextEncoder();
5528
5566
  var getBodySize = (body) => {
5529
5567
  if (!body) {
5530
5568
  return 0;
@@ -5533,33 +5571,22 @@ var getBodySize = (body) => {
5533
5571
  let size = 0;
5534
5572
  for (const [key, value] of body) {
5535
5573
  size += usualFormBoundarySize;
5536
- size += new TextEncoder().encode(`Content-Disposition: form-data; name="${key}"`).length;
5537
- size += typeof value === "string" ? new TextEncoder().encode(value).length : value.size;
5574
+ size += encoder.encode(`Content-Disposition: form-data; name="${key}"`).byteLength;
5575
+ size += typeof value === "string" ? encoder.encode(value).byteLength : value.size;
5538
5576
  }
5539
5577
  return size;
5540
5578
  }
5541
5579
  if (body instanceof Blob) {
5542
5580
  return body.size;
5543
5581
  }
5544
- if (body instanceof ArrayBuffer) {
5582
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
5545
5583
  return body.byteLength;
5546
5584
  }
5547
5585
  if (typeof body === "string") {
5548
- return new TextEncoder().encode(body).length;
5586
+ return encoder.encode(body).byteLength;
5549
5587
  }
5550
5588
  if (body instanceof URLSearchParams) {
5551
- return new TextEncoder().encode(body.toString()).length;
5552
- }
5553
- if ("byteLength" in body) {
5554
- return body.byteLength;
5555
- }
5556
- if (typeof body === "object" && body !== null) {
5557
- try {
5558
- const jsonString = JSON.stringify(body);
5559
- return new TextEncoder().encode(jsonString).length;
5560
- } catch {
5561
- return 0;
5562
- }
5589
+ return encoder.encode(body.toString()).byteLength;
5563
5590
  }
5564
5591
  return 0;
5565
5592
  };
@@ -5591,19 +5618,16 @@ var streamResponse = (response, onDownloadProgress) => {
5591
5618
  if (!response.body) {
5592
5619
  return response;
5593
5620
  }
5594
- if (response.status === 204) {
5595
- return new Response(null, {
5596
- status: response.status,
5597
- statusText: response.statusText,
5598
- headers: response.headers
5599
- });
5600
- }
5601
- const totalBytes = Math.max(0, Number(response.headers.get("content-length")) || 0);
5602
- return new Response(withProgress(response.body, totalBytes, onDownloadProgress), {
5621
+ const responseInit = {
5603
5622
  status: response.status,
5604
5623
  statusText: response.statusText,
5605
5624
  headers: response.headers
5606
- });
5625
+ };
5626
+ if (response.status === 204) {
5627
+ return new Response(null, responseInit);
5628
+ }
5629
+ const totalBytes = Math.max(0, Number(response.headers.get("content-length")) || 0);
5630
+ return new Response(withProgress(response.body, totalBytes, onDownloadProgress), responseInit);
5607
5631
  };
5608
5632
  var streamRequest = (request, onUploadProgress, originalBody) => {
5609
5633
  if (!request.body) {
@@ -5617,10 +5641,18 @@ var streamRequest = (request, onUploadProgress, originalBody) => {
5617
5641
  });
5618
5642
  };
5619
5643
 
5620
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/is.js
5644
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/is.js
5621
5645
  var isObject = (value) => value !== null && typeof value === "object";
5622
5646
 
5623
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/merge.js
5647
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/merge.js
5648
+ var replaceSymbol = /* @__PURE__ */ Symbol("replaceOption");
5649
+ var getReplaceState = (value) => isObject(value) && value[replaceSymbol] === true ? {
5650
+ isReplace: true,
5651
+ value: value.value
5652
+ } : {
5653
+ isReplace: false,
5654
+ value
5655
+ };
5624
5656
  var validateAndMerge = (...sources) => {
5625
5657
  for (const source of sources) {
5626
5658
  if ((!isObject(source) || Array.isArray(source)) && source !== void 0) {
@@ -5642,17 +5674,55 @@ var mergeHeaders = (source1 = {}, source2 = {}) => {
5642
5674
  }
5643
5675
  return result;
5644
5676
  };
5677
+ var isPlainObject = (value) => {
5678
+ if (!isObject(value) || Array.isArray(value)) {
5679
+ return false;
5680
+ }
5681
+ const prototype = Object.getPrototypeOf(value);
5682
+ return prototype === Object.prototype || prototype === null;
5683
+ };
5684
+ var cloneShallow = (value) => {
5685
+ if (value instanceof URLSearchParams) {
5686
+ const copy = new URLSearchParams(value);
5687
+ const deleted = value[deletedParametersSymbol];
5688
+ if (deleted) {
5689
+ copy[deletedParametersSymbol] = new Set(deleted);
5690
+ }
5691
+ return copy;
5692
+ }
5693
+ if (value instanceof globalThis.Headers) {
5694
+ return new globalThis.Headers(value);
5695
+ }
5696
+ if (Array.isArray(value)) {
5697
+ return [...value];
5698
+ }
5699
+ if (isPlainObject(value)) {
5700
+ const copy = { ...value };
5701
+ return copy;
5702
+ }
5703
+ return value;
5704
+ };
5705
+ var normalizeHeaderObject = (headers) => Object.fromEntries(Object.entries(headers).filter((entry) => entry[1] !== void 0));
5706
+ var mergeHeaderContainers = (source1, source2) => {
5707
+ if (isPlainObject(source1) && isPlainObject(source2)) {
5708
+ return normalizeHeaderObject({ ...source1, ...source2 });
5709
+ }
5710
+ return mergeHeaders(source1, source2);
5711
+ };
5645
5712
  function newHookValue(original, incoming, property) {
5646
5713
  return Object.hasOwn(incoming, property) && incoming[property] === void 0 ? [] : deepMerge(original[property] ?? [], incoming[property] ?? []);
5647
5714
  }
5648
5715
  var mergeHooks = (original = {}, incoming = {}) => ({
5716
+ init: newHookValue(original, incoming, "init"),
5649
5717
  beforeRequest: newHookValue(original, incoming, "beforeRequest"),
5650
5718
  beforeRetry: newHookValue(original, incoming, "beforeRetry"),
5651
- afterResponse: newHookValue(original, incoming, "afterResponse"),
5652
- beforeError: newHookValue(original, incoming, "beforeError")
5719
+ beforeError: newHookValue(original, incoming, "beforeError"),
5720
+ afterResponse: newHookValue(original, incoming, "afterResponse")
5653
5721
  });
5722
+ var deletedParametersSymbol = /* @__PURE__ */ Symbol("deletedParameters");
5654
5723
  var appendSearchParameters = (target, source) => {
5655
5724
  const result = new URLSearchParams();
5725
+ const deleted = /* @__PURE__ */ new Set();
5656
5726
  for (const input of [target, source]) {
5657
5727
  if (input === void 0) {
5658
5728
  continue;
@@ -5660,6 +5730,14 @@ var appendSearchParameters = (target, source) => {
5660
5730
  if (input instanceof URLSearchParams) {
5661
5731
  for (const [key, value] of input.entries()) {
5662
5732
  result.append(key, value);
5733
+ deleted.delete(key);
5734
+ }
5735
+ const inputDeleted = input[deletedParametersSymbol];
5736
+ if (inputDeleted) {
5737
+ for (const key of inputDeleted) {
5738
+ result.delete(key);
5739
+ deleted.add(key);
5740
+ }
5663
5741
  }
5664
5742
  } else if (Array.isArray(input)) {
5665
5743
  for (const pair of input) {
@@ -5667,20 +5745,29 @@ var appendSearchParameters = (target, source) => {
5667
5745
  throw new TypeError("Array search parameters must be provided in [[key, value], ...] format");
5668
5746
  }
5669
5747
  result.append(String(pair[0]), String(pair[1]));
5748
+ deleted.delete(String(pair[0]));
5670
5749
  }
5671
5750
  } else if (isObject(input)) {
5672
5751
  for (const [key, value] of Object.entries(input)) {
5673
- if (value !== void 0) {
5752
+ if (value === void 0) {
5753
+ result.delete(key);
5754
+ deleted.add(key);
5755
+ } else {
5674
5756
  result.append(key, String(value));
5757
+ deleted.delete(key);
5675
5758
  }
5676
5759
  }
5677
5760
  } else {
5678
5761
  const parameters = new URLSearchParams(input);
5679
5762
  for (const [key, value] of parameters.entries()) {
5680
5763
  result.append(key, value);
5764
+ deleted.delete(key);
5681
5765
  }
5682
5766
  }
5683
5767
  }
5768
+ if (deleted.size > 0) {
5769
+ result[deletedParametersSymbol] = deleted;
5770
+ }
5684
5771
  return result;
5685
5772
  };
5686
5773
  var deepMerge = (...sources) => {
@@ -5701,35 +5788,42 @@ var deepMerge = (...sources) => {
5701
5788
  signals2.push(value);
5702
5789
  continue;
5703
5790
  }
5791
+ const replaceState = getReplaceState(value);
5792
+ const { isReplace } = replaceState;
5793
+ value = replaceState.value;
5704
5794
  if (key === "context") {
5705
5795
  if (value !== void 0 && value !== null && (!isObject(value) || Array.isArray(value))) {
5706
5796
  throw new TypeError("The `context` option must be an object");
5707
5797
  }
5708
5798
  returnValue = {
5709
5799
  ...returnValue,
5710
- context: value === void 0 || value === null ? {} : { ...returnValue.context, ...value }
5800
+ context: value === void 0 || value === null ? {} : isReplace ? { ...value } : { ...returnValue.context, ...value }
5711
5801
  };
5712
5802
  continue;
5713
5803
  }
5714
5804
  if (key === "searchParams") {
5715
5805
  if (value === void 0 || value === null) {
5716
5806
  searchParameters = void 0;
5807
+ } else if (isReplace) {
5808
+ searchParameters = value;
5717
5809
  } else {
5718
5810
  searchParameters = searchParameters === void 0 ? value : appendSearchParameters(searchParameters, value);
5719
5811
  }
5720
5812
  continue;
5721
5813
  }
5722
- if (isObject(value) && key in returnValue) {
5814
+ if (isObject(value) && !isReplace && key in returnValue) {
5723
5815
  value = deepMerge(returnValue[key], value);
5724
5816
  }
5725
5817
  returnValue = { ...returnValue, [key]: value };
5726
5818
  }
5727
5819
  if (isObject(source.hooks)) {
5728
- hooks = mergeHooks(hooks, source.hooks);
5820
+ const { value: hookValue, isReplace } = getReplaceState(source.hooks);
5821
+ hooks = isReplace ? mergeHooks({}, hookValue) : mergeHooks(hooks, hookValue);
5729
5822
  returnValue.hooks = hooks;
5730
5823
  }
5731
5824
  if (isObject(source.headers)) {
5732
- headers = mergeHeaders(headers, source.headers);
5825
+ const { value: headerValue, isReplace } = getReplaceState(source.headers);
5826
+ headers = isReplace ? cloneShallow(headerValue) : mergeHeaderContainers(headers, headerValue);
5733
5827
  returnValue.headers = headers;
5734
5828
  }
5735
5829
  }
@@ -5749,7 +5843,7 @@ var deepMerge = (...sources) => {
5749
5843
  return returnValue;
5750
5844
  };
5751
5845
 
5752
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/normalize.js
5846
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/normalize.js
5753
5847
  var normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input;
5754
5848
  var retryMethods = ["get", "put", "head", "delete", "options", "trace"];
5755
5849
  var retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
@@ -5775,28 +5869,20 @@ var normalizeRetryOptions = (retry2 = {}) => {
5775
5869
  if (retry2.methods && !Array.isArray(retry2.methods)) {
5776
5870
  throw new Error("retry.methods must be an array");
5777
5871
  }
5778
- retry2.methods &&= retry2.methods.map((method) => method.toLowerCase());
5779
5872
  if (retry2.statusCodes && !Array.isArray(retry2.statusCodes)) {
5780
5873
  throw new Error("retry.statusCodes must be an array");
5781
5874
  }
5782
- const normalizedRetry = Object.fromEntries(Object.entries(retry2).filter(([, value]) => value !== void 0));
5875
+ const normalizedRetry = Object.fromEntries(Object.entries({
5876
+ ...retry2,
5877
+ methods: retry2.methods?.map((method) => method.toLowerCase())
5878
+ }).filter(([, value]) => value !== void 0));
5783
5879
  return {
5784
5880
  ...defaultRetryOptions,
5785
5881
  ...normalizedRetry
5786
5882
  };
5787
5883
  };
5788
5884
 
5789
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/errors/TimeoutError.js
5790
- var TimeoutError = class extends Error {
5791
- request;
5792
- constructor(request) {
5793
- super(`Request timed out: ${request.method} ${request.url}`);
5794
- this.name = "TimeoutError";
5795
- this.request = request;
5796
- }
5797
- };
5798
-
5799
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/timeout.js
5885
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/timeout.js
5800
5886
  async function timeout(request, init, abortController, options) {
5801
5887
  return new Promise((resolve, reject) => {
5802
5888
  const timeoutId = setTimeout(() => {
@@ -5811,7 +5897,7 @@ async function timeout(request, init, abortController, options) {
5811
5897
  });
5812
5898
  }
5813
5899
 
5814
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/delay.js
5900
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/delay.js
5815
5901
  async function delay(ms, { signal }) {
5816
5902
  return new Promise((resolve, reject) => {
5817
5903
  if (signal) {
@@ -5829,14 +5915,14 @@ async function delay(ms, { signal }) {
5829
5915
  });
5830
5916
  }
5831
5917
 
5832
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/options.js
5833
- var findUnknownOptions = (request, options) => {
5918
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/options.js
5919
+ var findUnknownOptions = (options) => {
5834
5920
  const unknownOptions = {};
5835
5921
  for (const key in options) {
5836
5922
  if (!Object.hasOwn(options, key)) {
5837
5923
  continue;
5838
5924
  }
5839
- if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys) && (!(key in request) || key in vendorSpecificOptions)) {
5925
+ if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys)) {
5840
5926
  unknownOptions[key] = options[key];
5841
5927
  }
5842
5928
  }
@@ -5850,7 +5936,7 @@ var hasSearchParameters = (search) => {
5850
5936
  return search.length > 0;
5851
5937
  }
5852
5938
  if (search instanceof URLSearchParams) {
5853
- return search.size > 0;
5939
+ return search.size > 0 || Boolean(search[deletedParametersSymbol]?.size);
5854
5940
  }
5855
5941
  if (typeof search === "object") {
5856
5942
  return Object.keys(search).length > 0;
@@ -5861,56 +5947,181 @@ var hasSearchParameters = (search) => {
5861
5947
  return Boolean(search);
5862
5948
  };
5863
5949
 
5864
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/type-guards.js
5950
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/is-network-error.js
5951
+ var objectToString = Object.prototype.toString;
5952
+ var isError = (value) => objectToString.call(value) === "[object Error]";
5953
+ var errorMessages = /* @__PURE__ */ new Set([
5954
+ "network error",
5955
+ // Chrome
5956
+ "NetworkError when attempting to fetch resource.",
5957
+ // Firefox
5958
+ "The Internet connection appears to be offline.",
5959
+ // Safari 16
5960
+ "Network request failed",
5961
+ // `cross-fetch`
5962
+ "fetch failed",
5963
+ // Undici (Node.js)
5964
+ "terminated",
5965
+ // Undici (Node.js)
5966
+ " A network error occurred.",
5967
+ // Bun (WebKit) - leading space is intentional
5968
+ "Network connection lost"
5969
+ // Cloudflare Workers (fetch)
5970
+ ]);
5971
+ function isRawNetworkError(error2) {
5972
+ const isValid = error2 && isError(error2) && error2.name === "TypeError" && typeof error2.message === "string";
5973
+ if (!isValid) {
5974
+ return false;
5975
+ }
5976
+ const { message, stack } = error2;
5977
+ if (message === "Load failed") {
5978
+ return stack === void 0 || "__sentry_captured__" in error2;
5979
+ }
5980
+ if (message.startsWith("error sending request for url")) {
5981
+ return true;
5982
+ }
5983
+ if (message === "Failed to fetch" || message.startsWith("Failed to fetch (") && message.endsWith(")")) {
5984
+ return true;
5985
+ }
5986
+ return errorMessages.has(message);
5987
+ }
5988
+
5989
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/utils/type-guards.js
5990
+ var isErrorType = (error2, cls) => error2 instanceof cls || error2?.name === cls.name;
5865
5991
  function isHTTPError(error2) {
5866
- return error2 instanceof HTTPError || error2?.name === HTTPError.name;
5992
+ return isErrorType(error2, HTTPError);
5993
+ }
5994
+ function isNetworkError(error2) {
5995
+ return isErrorType(error2, NetworkError);
5867
5996
  }
5868
5997
  function isTimeoutError(error2) {
5869
- return error2 instanceof TimeoutError || error2?.name === TimeoutError.name;
5998
+ return isErrorType(error2, TimeoutError);
5870
5999
  }
5871
6000
 
5872
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/core/Ky.js
6001
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/core/Ky.js
6002
+ var maxErrorResponseBodySize = 10 * 1024 * 1024;
6003
+ var prefixUrlRenamedErrorMessage = "The `prefixUrl` option has been renamed `prefix` in v2 and enhanced to allow slashes in input. See also the new `baseUrl` option for improved flexibility with standard URL resolution: https://github.com/sindresorhus/ky#baseurl";
6004
+ var timedOutResponseData = /* @__PURE__ */ Symbol("timedOutResponseData");
6005
+ var createTextDecoder = (contentType) => {
6006
+ const match = /;\s*charset\s*=\s*(?:"([^"]+)"|([^;,\s]+))/i.exec(contentType);
6007
+ const charset = match?.[1] ?? match?.[2];
6008
+ if (charset) {
6009
+ try {
6010
+ return new TextDecoder(charset);
6011
+ } catch {
6012
+ }
6013
+ }
6014
+ return new TextDecoder();
6015
+ };
6016
+ var invalidSchemaMessage = "The `schema` argument must follow the Standard Schema specification";
6017
+ var cloneRetryOptions = (retry2) => {
6018
+ if (typeof retry2 !== "object") {
6019
+ return retry2;
6020
+ }
6021
+ return {
6022
+ ...retry2,
6023
+ ...retry2.methods && { methods: [...retry2.methods] },
6024
+ ...retry2.statusCodes && { statusCodes: [...retry2.statusCodes] },
6025
+ ...retry2.afterStatusCodes && { afterStatusCodes: [...retry2.afterStatusCodes] }
6026
+ };
6027
+ };
6028
+ var objectToString2 = Object.prototype.toString;
6029
+ var isRequestInstance = (value) => value instanceof globalThis.Request || objectToString2.call(value) === "[object Request]";
6030
+ var isResponseInstance = (value) => value instanceof globalThis.Response || objectToString2.call(value) === "[object Response]";
6031
+ var cloneSearchParametersForInitHook = (searchParameters) => {
6032
+ if (Array.isArray(searchParameters)) {
6033
+ return searchParameters.map((parameter) => [...parameter]);
6034
+ }
6035
+ return cloneShallow(searchParameters);
6036
+ };
6037
+ function cloneInitHookOptions(options) {
6038
+ const clonedOptions = {
6039
+ ...options,
6040
+ json: cloneShallow(options.json),
6041
+ context: cloneShallow(options.context),
6042
+ headers: cloneShallow(options.headers),
6043
+ searchParams: cloneSearchParametersForInitHook(options.searchParams)
6044
+ };
6045
+ if (options.retry !== void 0) {
6046
+ clonedOptions.retry = cloneRetryOptions(options.retry);
6047
+ }
6048
+ return clonedOptions;
6049
+ }
6050
+ var validateJsonWithSchema = async (jsonValue, schema) => {
6051
+ if (typeof schema !== "object" && typeof schema !== "function" || schema === null) {
6052
+ throw new TypeError(invalidSchemaMessage);
6053
+ }
6054
+ const standardSchema = schema["~standard"];
6055
+ if (typeof standardSchema !== "object" || standardSchema === null || typeof standardSchema.validate !== "function") {
6056
+ throw new TypeError(invalidSchemaMessage);
6057
+ }
6058
+ const validationResult = await standardSchema.validate(jsonValue);
6059
+ if (validationResult.issues) {
6060
+ throw new SchemaValidationError(validationResult.issues);
6061
+ }
6062
+ return validationResult.value;
6063
+ };
5873
6064
  var Ky = class _Ky {
5874
6065
  static create(input, options) {
5875
- const ky2 = new _Ky(input, options);
6066
+ const initHooks = options.hooks?.init ?? [];
6067
+ const initHookOptions = initHooks.length > 0 ? cloneInitHookOptions(options) : options;
6068
+ for (const hook of initHooks) {
6069
+ hook(initHookOptions);
6070
+ }
6071
+ const ky2 = new _Ky(input, initHookOptions);
5876
6072
  const function_ = async () => {
5877
6073
  if (typeof ky2.#options.timeout === "number" && ky2.#options.timeout > maxSafeTimeout) {
5878
6074
  throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
5879
6075
  }
6076
+ if (typeof ky2.#options.totalTimeout === "number" && ky2.#options.totalTimeout > maxSafeTimeout) {
6077
+ throw new RangeError(`The \`totalTimeout\` option cannot be greater than ${maxSafeTimeout}`);
6078
+ }
5880
6079
  await Promise.resolve();
5881
- let response = await ky2.#fetch();
5882
- for (const hook of ky2.#options.hooks.afterResponse) {
5883
- const clonedResponse = ky2.#decorateResponse(response.clone());
5884
- let modifiedResponse;
5885
- try {
5886
- modifiedResponse = await hook(ky2.request, ky2.#getNormalizedOptions(), clonedResponse, { retryCount: ky2.#retryCount });
5887
- } catch (error2) {
5888
- ky2.#cancelResponseBody(clonedResponse);
5889
- ky2.#cancelResponseBody(response);
5890
- throw error2;
5891
- }
5892
- if (modifiedResponse instanceof RetryMarker) {
5893
- ky2.#cancelResponseBody(clonedResponse);
5894
- ky2.#cancelResponseBody(response);
5895
- throw new ForceRetryError(modifiedResponse.options);
5896
- }
5897
- const nextResponse = modifiedResponse instanceof globalThis.Response ? modifiedResponse : response;
5898
- if (clonedResponse !== nextResponse) {
5899
- ky2.#cancelResponseBody(clonedResponse);
6080
+ const beforeRequestResponse = await ky2.#runBeforeRequestHooks();
6081
+ let response = beforeRequestResponse ?? await ky2.#retry(async () => ky2.#fetch());
6082
+ let responseFromHook = beforeRequestResponse !== void 0 || ky2.#consumeReturnedResponseFromBeforeRetryHook();
6083
+ for (; ; ) {
6084
+ if (response === void 0) {
6085
+ return response;
6086
+ }
6087
+ if (isResponseInstance(response)) {
6088
+ try {
6089
+ response = await ky2.#runAfterResponseHooks(response);
6090
+ } catch (error2) {
6091
+ if (!(error2 instanceof ForceRetryError)) {
6092
+ throw error2;
6093
+ }
6094
+ const retriedResponse = await ky2.#retryFromError(error2, async () => ky2.#fetch());
6095
+ if (retriedResponse === void 0) {
6096
+ return retriedResponse;
6097
+ }
6098
+ response = retriedResponse;
6099
+ responseFromHook = ky2.#consumeReturnedResponseFromBeforeRetryHook();
6100
+ continue;
6101
+ }
5900
6102
  }
5901
- if (response !== nextResponse) {
5902
- ky2.#cancelResponseBody(response);
6103
+ const currentResponse = response;
6104
+ if (!currentResponse.ok && currentResponse.type !== "opaque" && (typeof ky2.#options.throwHttpErrors === "function" ? ky2.#options.throwHttpErrors(currentResponse.status) : ky2.#options.throwHttpErrors)) {
6105
+ const httpError = new HTTPError(currentResponse, ky2.#getResponseRequest(currentResponse), ky2.#getNormalizedOptions());
6106
+ const errorToThrow = httpError;
6107
+ httpError.data = await ky2.#getResponseData(currentResponse);
6108
+ if (responseFromHook) {
6109
+ throw errorToThrow;
6110
+ }
6111
+ const retriedResponse = await ky2.#retryFromError(httpError, async () => ky2.#fetch());
6112
+ if (retriedResponse === void 0) {
6113
+ return retriedResponse;
6114
+ }
6115
+ response = retriedResponse;
6116
+ responseFromHook = ky2.#consumeReturnedResponseFromBeforeRetryHook();
6117
+ continue;
5903
6118
  }
5904
- response = nextResponse;
6119
+ break;
5905
6120
  }
5906
- ky2.#decorateResponse(response);
5907
- if (!response.ok && (typeof ky2.#options.throwHttpErrors === "function" ? ky2.#options.throwHttpErrors(response.status) : ky2.#options.throwHttpErrors)) {
5908
- let error2 = new HTTPError(response, ky2.request, ky2.#getNormalizedOptions());
5909
- for (const hook of ky2.#options.hooks.beforeError) {
5910
- error2 = await hook(error2, { retryCount: ky2.#retryCount });
5911
- }
5912
- throw error2;
6121
+ if (!isResponseInstance(response)) {
6122
+ return response;
5913
6123
  }
6124
+ ky2.#decorateResponse(response);
5914
6125
  if (ky2.#options.onDownloadProgress) {
5915
6126
  if (typeof ky2.#options.onDownloadProgress !== "function") {
5916
6127
  throw new TypeError("The `onDownloadProgress` option must be a function");
@@ -5924,32 +6135,56 @@ var Ky = class _Ky {
5924
6135
  }
5925
6136
  return response;
5926
6137
  };
5927
- const result = ky2.#retry(function_).finally(() => {
5928
- const originalRequest = ky2.#originalRequest;
5929
- ky2.#cancelBody(originalRequest?.body ?? void 0);
5930
- ky2.#cancelBody(ky2.request.body ?? void 0);
5931
- });
6138
+ const result = (async () => {
6139
+ try {
6140
+ return await function_();
6141
+ } catch (error2) {
6142
+ if (!(error2 instanceof Error)) {
6143
+ throw error2;
6144
+ }
6145
+ if (ky2.#beforeRetryHookErrors.has(error2)) {
6146
+ throw error2;
6147
+ }
6148
+ let processedError = error2;
6149
+ for (const hook of ky2.#options.hooks.beforeError) {
6150
+ const hookResult = await hook({
6151
+ request: ky2.request,
6152
+ options: ky2.#getNormalizedOptions(),
6153
+ error: processedError,
6154
+ retryCount: ky2.#retryCount
6155
+ });
6156
+ if (hookResult instanceof Error) {
6157
+ processedError = hookResult;
6158
+ }
6159
+ }
6160
+ throw processedError;
6161
+ } finally {
6162
+ const originalRequest = ky2.#originalRequest;
6163
+ ky2.#cancelBody(originalRequest?.body ?? void 0);
6164
+ if (ky2.request !== originalRequest) {
6165
+ ky2.#cancelBody(ky2.request.body ?? void 0);
6166
+ }
6167
+ }
6168
+ })();
5932
6169
  for (const [type, mimeType] of Object.entries(responseTypes)) {
5933
6170
  if (type === "bytes" && typeof globalThis.Response?.prototype?.bytes !== "function") {
5934
6171
  continue;
5935
6172
  }
5936
- result[type] = async () => {
6173
+ result[type] = async (schema) => {
5937
6174
  ky2.request.headers.set("accept", ky2.request.headers.get("accept") || mimeType);
5938
6175
  const response = await result;
5939
- if (type === "json") {
5940
- if (response.status === 204) {
5941
- return "";
5942
- }
5943
- const text = await response.text();
5944
- if (text === "") {
5945
- return "";
5946
- }
5947
- if (options.parseJson) {
5948
- return options.parseJson(text);
6176
+ if (type !== "json") {
6177
+ return response[type]();
6178
+ }
6179
+ const text = await response.text();
6180
+ if (text === "") {
6181
+ if (schema !== void 0) {
6182
+ return validateJsonWithSchema(void 0, schema);
5949
6183
  }
5950
6184
  return JSON.parse(text);
5951
6185
  }
5952
- return response[type]();
6186
+ const jsonValue = initHookOptions.parseJson ? await initHookOptions.parseJson(text, { request: ky2.#getResponseRequest(response), response }) : JSON.parse(text);
6187
+ return schema === void 0 ? jsonValue : validateJsonWithSchema(jsonValue, schema);
5953
6188
  };
5954
6189
  }
5955
6190
  return result;
@@ -5969,44 +6204,55 @@ var Ky = class _Ky {
5969
6204
  #options;
5970
6205
  #originalRequest;
5971
6206
  #userProvidedAbortSignal;
6207
+ #beforeRetryHookErrors = /* @__PURE__ */ new WeakSet();
5972
6208
  #cachedNormalizedOptions;
6209
+ #startTime;
6210
+ #returnedResponseFromBeforeRetryHook = false;
6211
+ #responseRequests = /* @__PURE__ */ new WeakMap();
5973
6212
  // eslint-disable-next-line complexity
5974
6213
  constructor(input, options = {}) {
5975
6214
  this.#input = input;
6215
+ if (Object.hasOwn(options, "prefixUrl")) {
6216
+ throw new Error(prefixUrlRenamedErrorMessage);
6217
+ }
5976
6218
  this.#options = {
5977
6219
  ...options,
5978
6220
  headers: mergeHeaders(this.#input.headers, options.headers),
5979
- hooks: mergeHooks({
5980
- beforeRequest: [],
5981
- beforeRetry: [],
5982
- beforeError: [],
5983
- afterResponse: []
5984
- }, options.hooks),
6221
+ hooks: mergeHooks({}, options.hooks),
5985
6222
  method: normalizeRequestMethod(options.method ?? this.#input.method ?? "GET"),
5986
6223
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
5987
- prefixUrl: String(options.prefixUrl || ""),
6224
+ prefix: String(options.prefix || ""),
5988
6225
  retry: normalizeRetryOptions(options.retry),
5989
6226
  throwHttpErrors: options.throwHttpErrors ?? true,
5990
6227
  timeout: options.timeout ?? 1e4,
6228
+ totalTimeout: options.totalTimeout ?? false,
5991
6229
  fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
5992
6230
  context: options.context ?? {}
5993
6231
  };
5994
6232
  if (typeof this.#input !== "string" && !(this.#input instanceof URL || this.#input instanceof globalThis.Request)) {
5995
6233
  throw new TypeError("`input` must be a string, URL, or Request");
5996
6234
  }
5997
- if (this.#options.prefixUrl && typeof this.#input === "string") {
5998
- if (this.#input.startsWith("/")) {
5999
- throw new Error("`input` must not begin with a slash when using `prefixUrl`");
6235
+ if (typeof this.#input === "string") {
6236
+ if (this.#options.prefix) {
6237
+ const normalizedPrefix = this.#options.prefix.replace(/\/+$/, "");
6238
+ const normalizedInput = this.#input.replace(/^\/+/, "");
6239
+ this.#input = `${normalizedPrefix}/${normalizedInput}`;
6000
6240
  }
6001
- if (!this.#options.prefixUrl.endsWith("/")) {
6002
- this.#options.prefixUrl += "/";
6241
+ if (this.#options.baseUrl) {
6242
+ let absoluteInput;
6243
+ try {
6244
+ absoluteInput = new URL(this.#input);
6245
+ } catch {
6246
+ }
6247
+ if (!absoluteInput) {
6248
+ this.#input = new URL(this.#input, new Request(this.#options.baseUrl).url);
6249
+ }
6003
6250
  }
6004
- this.#input = this.#options.prefixUrl + this.#input;
6005
6251
  }
6006
6252
  if (supportsAbortController && supportsAbortSignal) {
6007
6253
  this.#userProvidedAbortSignal = this.#options.signal ?? this.#input.signal;
6008
6254
  this.#abortController = new globalThis.AbortController();
6009
- this.#options.signal = this.#userProvidedAbortSignal ? AbortSignal.any([this.#userProvidedAbortSignal, this.#abortController.signal]) : this.#abortController.signal;
6255
+ this.#options.signal = this.#createManagedSignal();
6010
6256
  }
6011
6257
  if (supportsRequestStreams) {
6012
6258
  this.#options.duplex = "half";
@@ -6021,23 +6267,40 @@ var Ky = class _Ky {
6021
6267
  }
6022
6268
  this.request = new globalThis.Request(this.#input, this.#options);
6023
6269
  if (hasSearchParameters(this.#options.searchParams)) {
6024
- const textSearchParams = typeof this.#options.searchParams === "string" ? this.#options.searchParams.replace(/^\?/, "") : new URLSearchParams(_Ky.#normalizeSearchParams(this.#options.searchParams)).toString();
6025
- const searchParams = "?" + textSearchParams;
6026
- const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);
6027
- this.request = new globalThis.Request(url, this.#options);
6028
- }
6029
- if (this.#options.onUploadProgress) {
6030
- if (typeof this.#options.onUploadProgress !== "function") {
6031
- throw new TypeError("The `onUploadProgress` option must be a function");
6270
+ const url = new URL(this.request.url);
6271
+ const deleted = this.#options.searchParams?.[deletedParametersSymbol];
6272
+ if (deleted) {
6273
+ for (const key of deleted) {
6274
+ url.searchParams.delete(key);
6275
+ }
6276
+ }
6277
+ if (typeof this.#options.searchParams === "string") {
6278
+ const stringSearchParameters = this.#options.searchParams.replace(/^\?/, "");
6279
+ if (stringSearchParameters !== "") {
6280
+ url.search = url.search ? `${url.search}&${stringSearchParameters}` : `?${stringSearchParameters}`;
6281
+ }
6282
+ } else {
6283
+ const optionsSearchParameters = new URLSearchParams(_Ky.#normalizeSearchParams(this.#options.searchParams));
6284
+ for (const [key, value] of optionsSearchParameters.entries()) {
6285
+ url.searchParams.append(key, value);
6286
+ }
6032
6287
  }
6033
- if (!supportsRequestStreams) {
6034
- throw new Error("Request streams are not supported in your environment. The `duplex` option for `Request` is not available.");
6288
+ if (this.#options.searchParams && typeof this.#options.searchParams === "object" && !Array.isArray(this.#options.searchParams) && !(this.#options.searchParams instanceof URLSearchParams)) {
6289
+ for (const [key, value] of Object.entries(this.#options.searchParams)) {
6290
+ if (value === void 0) {
6291
+ url.searchParams.delete(key);
6292
+ }
6293
+ }
6035
6294
  }
6036
- this.request = this.#wrapRequestWithUploadProgress(this.request, this.#options.body ?? void 0);
6295
+ this.request = new globalThis.Request(url, this.#options);
6296
+ }
6297
+ if (this.#options.onUploadProgress && typeof this.#options.onUploadProgress !== "function") {
6298
+ throw new TypeError("The `onUploadProgress` option must be a function");
6037
6299
  }
6300
+ this.#startTime = typeof this.#options.totalTimeout === "number" ? this.#getCurrentTime() : void 0;
6038
6301
  }
6039
6302
  #calculateDelay() {
6040
- const retryDelay = this.#options.retry.delay(this.#retryCount);
6303
+ const retryDelay = this.#options.retry.delay(this.#retryCount + 1);
6041
6304
  let jitteredDelay = retryDelay;
6042
6305
  if (this.#options.retry.jitter === true) {
6043
6306
  jitteredDelay = Math.random() * retryDelay;
@@ -6047,12 +6310,10 @@ var Ky = class _Ky {
6047
6310
  jitteredDelay = retryDelay;
6048
6311
  }
6049
6312
  }
6050
- const backoffLimit = this.#options.retry.backoffLimit ?? Number.POSITIVE_INFINITY;
6051
- return Math.min(backoffLimit, jitteredDelay);
6313
+ return Math.min(this.#options.retry.backoffLimit, jitteredDelay);
6052
6314
  }
6053
6315
  async #calculateRetryDelay(error2) {
6054
- this.#retryCount++;
6055
- if (this.#retryCount > this.#options.retry.limit) {
6316
+ if (this.#retryCount >= this.#options.retry.limit) {
6056
6317
  throw error2;
6057
6318
  }
6058
6319
  const errorObject = error2 instanceof Error ? error2 : new NonError(error2);
@@ -6063,7 +6324,7 @@ var Ky = class _Ky {
6063
6324
  throw error2;
6064
6325
  }
6065
6326
  if (this.#options.retry.shouldRetry !== void 0) {
6066
- const result = await this.#options.retry.shouldRetry({ error: errorObject, retryCount: this.#retryCount });
6327
+ const result = await this.#options.retry.shouldRetry({ error: errorObject, retryCount: this.#retryCount + 1 });
6067
6328
  if (result === false) {
6068
6329
  throw error2;
6069
6330
  }
@@ -6071,8 +6332,11 @@ var Ky = class _Ky {
6071
6332
  return this.#calculateDelay();
6072
6333
  }
6073
6334
  }
6074
- if (isTimeoutError(error2) && !this.#options.retry.retryOnTimeout) {
6075
- throw error2;
6335
+ if (isTimeoutError(error2)) {
6336
+ if (!this.#options.retry.retryOnTimeout) {
6337
+ throw error2;
6338
+ }
6339
+ return this.#calculateDelay();
6076
6340
  }
6077
6341
  if (isHTTPError(error2)) {
6078
6342
  if (!this.#options.retry.statusCodes.includes(error2.response.status)) {
@@ -6086,21 +6350,138 @@ var Ky = class _Ky {
6086
6350
  } else if (after >= Date.parse("2024-01-01")) {
6087
6351
  after -= Date.now();
6088
6352
  }
6089
- const max = this.#options.retry.maxRetryAfter ?? after;
6090
- return after < max ? after : max;
6353
+ if (!Number.isFinite(after)) {
6354
+ return Math.min(this.#options.retry.maxRetryAfter, this.#calculateDelay());
6355
+ }
6356
+ after = Math.max(0, after);
6357
+ return Math.min(this.#options.retry.maxRetryAfter, after);
6091
6358
  }
6092
6359
  if (error2.response.status === 413) {
6093
6360
  throw error2;
6094
6361
  }
6362
+ return this.#calculateDelay();
6363
+ }
6364
+ if (!isNetworkError(error2)) {
6365
+ throw error2;
6095
6366
  }
6096
6367
  return this.#calculateDelay();
6097
6368
  }
6098
6369
  #decorateResponse(response) {
6370
+ const request = this.#getResponseRequest(response);
6099
6371
  if (this.#options.parseJson) {
6100
- response.json = async () => this.#options.parseJson(await response.text());
6372
+ response.json = async () => {
6373
+ const text = await response.text();
6374
+ if (text === "") {
6375
+ return JSON.parse(text);
6376
+ }
6377
+ return this.#options.parseJson(text, { request, response });
6378
+ };
6101
6379
  }
6102
6380
  return response;
6103
6381
  }
6382
+ async #getResponseData(response) {
6383
+ const text = await this.#readResponseText(response, this.#getErrorDataTimeout());
6384
+ if (text === timedOutResponseData) {
6385
+ this.#throwIfTotalTimeoutExhausted();
6386
+ return void 0;
6387
+ }
6388
+ if (!text) {
6389
+ return void 0;
6390
+ }
6391
+ if (!this.#isJsonContentType(response.headers.get("content-type") ?? "")) {
6392
+ return text;
6393
+ }
6394
+ const data = await this.#parseJson(text, response, this.#getErrorDataTimeout(), this.#getResponseRequest(response));
6395
+ if (data === timedOutResponseData) {
6396
+ this.#throwIfTotalTimeoutExhausted();
6397
+ return void 0;
6398
+ }
6399
+ return data;
6400
+ }
6401
+ #getErrorDataTimeout() {
6402
+ const errorDataTimeout = this.#options.timeout === false ? 1e4 : this.#options.timeout;
6403
+ const remainingTotal = this.#getRemainingTotalTimeout();
6404
+ if (remainingTotal === void 0) {
6405
+ return errorDataTimeout;
6406
+ }
6407
+ if (remainingTotal <= 0) {
6408
+ throw new TimeoutError(this.request);
6409
+ }
6410
+ return Math.min(errorDataTimeout, remainingTotal);
6411
+ }
6412
+ #isJsonContentType(contentType) {
6413
+ const mimeType = (contentType.split(";", 1)[0] ?? "").trim().toLowerCase();
6414
+ return /\/(?:.*[.+-])?json$/.test(mimeType);
6415
+ }
6416
+ async #readResponseText(response, timeoutMs) {
6417
+ const { body } = response;
6418
+ if (!body) {
6419
+ try {
6420
+ return await response.text();
6421
+ } catch {
6422
+ return void 0;
6423
+ }
6424
+ }
6425
+ let reader;
6426
+ try {
6427
+ reader = body.getReader();
6428
+ } catch {
6429
+ return void 0;
6430
+ }
6431
+ const decoder = createTextDecoder(response.headers.get("content-type") ?? "");
6432
+ const chunks = [];
6433
+ let totalBytes = 0;
6434
+ const readAll = (async () => {
6435
+ try {
6436
+ for (; ; ) {
6437
+ const { done, value } = await reader.read();
6438
+ if (done) {
6439
+ break;
6440
+ }
6441
+ totalBytes += value.byteLength;
6442
+ if (totalBytes > maxErrorResponseBodySize) {
6443
+ void reader.cancel().catch(() => void 0);
6444
+ return void 0;
6445
+ }
6446
+ chunks.push(decoder.decode(value, { stream: true }));
6447
+ }
6448
+ } catch {
6449
+ return void 0;
6450
+ }
6451
+ chunks.push(decoder.decode());
6452
+ return chunks.join("");
6453
+ })();
6454
+ const timeoutPromise = new Promise((resolve) => {
6455
+ const timeoutId = setTimeout(() => {
6456
+ resolve(timedOutResponseData);
6457
+ }, timeoutMs);
6458
+ void readAll.finally(() => {
6459
+ clearTimeout(timeoutId);
6460
+ });
6461
+ });
6462
+ const result = await Promise.race([readAll, timeoutPromise]);
6463
+ if (result === timedOutResponseData) {
6464
+ void reader.cancel().catch(() => void 0);
6465
+ }
6466
+ return result;
6467
+ }
6468
+ async #parseJson(text, response, timeoutMs, request) {
6469
+ let timeoutId;
6470
+ try {
6471
+ return await Promise.race([
6472
+ Promise.resolve().then(() => this.#options.parseJson ? this.#options.parseJson(text, { request, response }) : JSON.parse(text)),
6473
+ new Promise((resolve) => {
6474
+ timeoutId = setTimeout(() => {
6475
+ resolve(timedOutResponseData);
6476
+ }, timeoutMs);
6477
+ })
6478
+ ]);
6479
+ } catch {
6480
+ return void 0;
6481
+ } finally {
6482
+ clearTimeout(timeoutId);
6483
+ }
6484
+ }
6104
6485
  #cancelBody(body) {
6105
6486
  if (!body) {
6106
6487
  return;
@@ -6110,84 +6491,200 @@ var Ky = class _Ky {
6110
6491
  #cancelResponseBody(response) {
6111
6492
  this.#cancelBody(response.body ?? void 0);
6112
6493
  }
6494
+ #createManagedSignal() {
6495
+ return this.#userProvidedAbortSignal ? AbortSignal.any([this.#userProvidedAbortSignal, this.#abortController.signal]) : this.#abortController.signal;
6496
+ }
6497
+ #throwIfTotalTimeoutExhausted() {
6498
+ const remaining = this.#getRemainingTotalTimeout();
6499
+ if (remaining !== void 0 && remaining <= 0) {
6500
+ throw new TimeoutError(this.request);
6501
+ }
6502
+ }
6503
+ async #runBeforeRequestHooks() {
6504
+ for (const hook of this.#options.hooks.beforeRequest) {
6505
+ const result = await hook({
6506
+ request: this.request,
6507
+ options: this.#getNormalizedOptions(),
6508
+ retryCount: 0
6509
+ });
6510
+ if (isRequestInstance(result)) {
6511
+ this.#assignRequest(result);
6512
+ } else if (isResponseInstance(result)) {
6513
+ return result;
6514
+ }
6515
+ }
6516
+ return void 0;
6517
+ }
6518
+ async #runAfterResponseHooks(response) {
6519
+ const responseRequest = this.#getResponseRequest(response);
6520
+ for (const hook of this.#options.hooks.afterResponse) {
6521
+ const hookResponse = this.#setResponseRequest(response.clone(), responseRequest);
6522
+ this.#decorateResponse(hookResponse);
6523
+ let modifiedResponse;
6524
+ try {
6525
+ modifiedResponse = await hook({
6526
+ request: this.request,
6527
+ options: this.#getNormalizedOptions(),
6528
+ response: hookResponse,
6529
+ retryCount: this.#retryCount
6530
+ });
6531
+ } catch (error2) {
6532
+ if (hookResponse !== response) {
6533
+ this.#cancelResponseBody(hookResponse);
6534
+ }
6535
+ this.#cancelResponseBody(response);
6536
+ throw error2;
6537
+ }
6538
+ if (modifiedResponse instanceof RetryMarker) {
6539
+ if (hookResponse !== response) {
6540
+ this.#cancelResponseBody(hookResponse);
6541
+ }
6542
+ this.#cancelResponseBody(response);
6543
+ throw new ForceRetryError(modifiedResponse.options);
6544
+ }
6545
+ const nextResponse = isResponseInstance(modifiedResponse) ? this.#setResponseRequest(modifiedResponse, responseRequest) : response;
6546
+ if (hookResponse !== response && hookResponse !== nextResponse && hookResponse.body !== nextResponse.body) {
6547
+ this.#cancelResponseBody(hookResponse);
6548
+ }
6549
+ if (response !== nextResponse && response.body !== nextResponse.body) {
6550
+ this.#cancelResponseBody(response);
6551
+ }
6552
+ response = nextResponse;
6553
+ }
6554
+ return response;
6555
+ }
6113
6556
  async #retry(function_) {
6114
6557
  try {
6115
6558
  return await function_();
6116
6559
  } catch (error2) {
6117
- const ms = Math.min(await this.#calculateRetryDelay(error2), maxSafeTimeout);
6118
- if (this.#retryCount < 1) {
6119
- throw error2;
6560
+ return this.#retryFromError(error2, function_);
6561
+ }
6562
+ }
6563
+ async #retryFromError(error2, function_) {
6564
+ this.#returnedResponseFromBeforeRetryHook = false;
6565
+ const retryDelay = Math.min(await this.#calculateRetryDelay(error2), maxSafeTimeout);
6566
+ const delayOptions = { signal: this.#userProvidedAbortSignal };
6567
+ const remainingTimeout = this.#getRemainingTotalTimeout();
6568
+ if (remainingTimeout !== void 0) {
6569
+ if (remainingTimeout <= 0) {
6570
+ throw new TimeoutError(this.request);
6120
6571
  }
6121
- await delay(ms, this.#userProvidedAbortSignal ? { signal: this.#userProvidedAbortSignal } : {});
6122
- if (error2 instanceof ForceRetryError && error2.customRequest) {
6123
- const managedRequest = this.#options.signal ? new globalThis.Request(error2.customRequest, { signal: this.#options.signal }) : new globalThis.Request(error2.customRequest);
6124
- this.#assignRequest(managedRequest);
6572
+ if (retryDelay >= remainingTimeout) {
6573
+ await delay(remainingTimeout, delayOptions);
6574
+ throw new TimeoutError(this.request);
6125
6575
  }
6126
- for (const hook of this.#options.hooks.beforeRetry) {
6127
- const hookResult = await hook({
6576
+ }
6577
+ await delay(retryDelay, delayOptions);
6578
+ this.#throwIfTotalTimeoutExhausted();
6579
+ if (error2 instanceof ForceRetryError && error2.customRequest) {
6580
+ const customRequest = new globalThis.Request(error2.customRequest, this.#options.signal ? { signal: this.#options.signal } : void 0);
6581
+ this.#assignRequest(customRequest);
6582
+ }
6583
+ for (const hook of this.#options.hooks.beforeRetry) {
6584
+ let hookResult;
6585
+ try {
6586
+ hookResult = await hook({
6128
6587
  request: this.request,
6129
6588
  options: this.#getNormalizedOptions(),
6130
6589
  error: error2,
6131
- retryCount: this.#retryCount
6590
+ retryCount: this.#retryCount + 1
6132
6591
  });
6133
- if (hookResult instanceof globalThis.Request) {
6134
- this.#assignRequest(hookResult);
6135
- break;
6136
- }
6137
- if (hookResult instanceof globalThis.Response) {
6138
- return hookResult;
6139
- }
6140
- if (hookResult === stop) {
6141
- return;
6592
+ } catch (hookError) {
6593
+ if (hookError instanceof Error && hookError !== error2) {
6594
+ this.#beforeRetryHookErrors.add(hookError);
6142
6595
  }
6596
+ throw hookError;
6597
+ }
6598
+ if (isRequestInstance(hookResult)) {
6599
+ this.#assignRequest(hookResult);
6600
+ break;
6601
+ }
6602
+ if (isResponseInstance(hookResult)) {
6603
+ this.#returnedResponseFromBeforeRetryHook = true;
6604
+ this.#retryCount++;
6605
+ return hookResult;
6606
+ }
6607
+ if (hookResult === stop) {
6608
+ return;
6143
6609
  }
6144
- return this.#retry(function_);
6145
6610
  }
6611
+ this.#throwIfTotalTimeoutExhausted();
6612
+ this.#retryCount++;
6613
+ return this.#retry(function_);
6614
+ }
6615
+ #consumeReturnedResponseFromBeforeRetryHook() {
6616
+ const value = this.#returnedResponseFromBeforeRetryHook;
6617
+ this.#returnedResponseFromBeforeRetryHook = false;
6618
+ return value;
6146
6619
  }
6147
6620
  async #fetch() {
6148
6621
  if (this.#abortController?.signal.aborted) {
6149
6622
  this.#abortController = new globalThis.AbortController();
6150
- this.#options.signal = this.#userProvidedAbortSignal ? AbortSignal.any([this.#userProvidedAbortSignal, this.#abortController.signal]) : this.#abortController.signal;
6623
+ this.#options.signal = this.#createManagedSignal();
6151
6624
  this.request = new globalThis.Request(this.request, { signal: this.#options.signal });
6152
6625
  }
6153
- for (const hook of this.#options.hooks.beforeRequest) {
6154
- const result = await hook(this.request, this.#getNormalizedOptions(), { retryCount: this.#retryCount });
6155
- if (result instanceof Response) {
6156
- return result;
6157
- }
6158
- if (result instanceof globalThis.Request) {
6159
- this.#assignRequest(result);
6160
- break;
6626
+ const nonRequestOptions = findUnknownOptions(this.#options);
6627
+ const retryRequest = this.#options.retry.limit > 0 ? this.request.clone() : void 0;
6628
+ const request = this.#wrapRequestWithUploadProgress(this.request, this.#options.body ?? void 0);
6629
+ this.#originalRequest = request;
6630
+ if (retryRequest) {
6631
+ this.request = retryRequest;
6632
+ }
6633
+ try {
6634
+ const remainingTotal = this.#getRemainingTotalTimeout();
6635
+ if (remainingTotal !== void 0 && remainingTotal <= 0) {
6636
+ throw new TimeoutError(this.request);
6637
+ }
6638
+ const effectiveTimeout = this.#options.timeout === false ? remainingTotal : remainingTotal === void 0 ? this.#options.timeout : Math.min(this.#options.timeout, remainingTotal);
6639
+ const response = effectiveTimeout === void 0 ? await this.#options.fetch(request, nonRequestOptions) : await timeout(request, nonRequestOptions, this.#abortController, {
6640
+ timeout: effectiveTimeout,
6641
+ fetch: this.#options.fetch
6642
+ });
6643
+ return this.#setResponseRequest(response, request);
6644
+ } catch (error2) {
6645
+ if (isRawNetworkError(error2)) {
6646
+ throw new NetworkError(this.request, { cause: error2 });
6161
6647
  }
6648
+ throw error2;
6162
6649
  }
6163
- const nonRequestOptions = findUnknownOptions(this.request, this.#options);
6164
- this.#originalRequest = this.request;
6165
- this.request = this.#originalRequest.clone();
6166
- if (this.#options.timeout === false) {
6167
- return this.#options.fetch(this.#originalRequest, nonRequestOptions);
6650
+ }
6651
+ #getRemainingTotalTimeout() {
6652
+ if (this.#startTime === void 0) {
6653
+ return void 0;
6168
6654
  }
6169
- return timeout(this.#originalRequest, nonRequestOptions, this.#abortController, this.#options);
6655
+ const elapsed = this.#getCurrentTime() - this.#startTime;
6656
+ return Math.max(0, this.#options.totalTimeout - elapsed);
6657
+ }
6658
+ #getCurrentTime() {
6659
+ return globalThis.performance?.now() ?? Date.now();
6170
6660
  }
6171
6661
  #getNormalizedOptions() {
6172
6662
  if (!this.#cachedNormalizedOptions) {
6173
- const { hooks, ...normalizedOptions } = this.#options;
6663
+ const { hooks, json, parseJson, stringifyJson, searchParams, timeout: timeout2, totalTimeout, throwHttpErrors, fetch: fetch2, ...normalizedOptions } = this.#options;
6174
6664
  this.#cachedNormalizedOptions = Object.freeze(normalizedOptions);
6175
6665
  }
6176
6666
  return this.#cachedNormalizedOptions;
6177
6667
  }
6178
6668
  #assignRequest(request) {
6179
6669
  this.#cachedNormalizedOptions = void 0;
6180
- this.request = this.#wrapRequestWithUploadProgress(request);
6670
+ this.request = request;
6671
+ }
6672
+ #getResponseRequest(response) {
6673
+ return this.#responseRequests.get(response) ?? this.request;
6674
+ }
6675
+ #setResponseRequest(response, request) {
6676
+ this.#responseRequests.set(response, request);
6677
+ return response;
6181
6678
  }
6182
6679
  #wrapRequestWithUploadProgress(request, originalBody) {
6183
- if (!this.#options.onUploadProgress || !request.body) {
6680
+ if (!this.#options.onUploadProgress || !request.body || !supportsRequestStreams) {
6184
6681
  return request;
6185
6682
  }
6186
6683
  return streamRequest(request, this.#options.onUploadProgress, originalBody ?? this.#options.body ?? void 0);
6187
6684
  }
6188
6685
  };
6189
6686
 
6190
- // ../../node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/index.js
6687
+ // ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/index.js
6191
6688
  var createInstance = (defaults) => {
6192
6689
  const ky2 = (input, options) => Ky.create(input, validateAndMerge(defaults, options));
6193
6690
  for (const method of requestMethods) {
@@ -6294,13 +6791,13 @@ var KinoSecClient = class {
6294
6791
  constructor(config) {
6295
6792
  const prefixPathname = new URL(config.apiUrl).pathname.replace(/\/?$/, "/");
6296
6793
  this.api = distribution_default.create({
6297
- prefixUrl: config.apiUrl,
6794
+ prefix: config.apiUrl,
6298
6795
  headers: {
6299
6796
  "user-agent": config.userAgent ?? `kinosec/0.1.0 (${process.platform})`
6300
6797
  },
6301
6798
  hooks: {
6302
6799
  beforeRequest: [
6303
- (request) => {
6800
+ ({ request }) => {
6304
6801
  request.headers.set("authorization", `Bearer ${config.token}`);
6305
6802
  const url = new URL(request.url);
6306
6803
  const path = url.pathname.slice(prefixPathname.length - 1);
@@ -6313,15 +6810,9 @@ var KinoSecClient = class {
6313
6810
  }
6314
6811
  ],
6315
6812
  beforeError: [
6316
- async (error2) => {
6317
- const { response } = error2;
6318
- let body;
6319
- try {
6320
- body = await response.json();
6321
- } catch {
6322
- body = null;
6323
- }
6324
- throw parseApiError(body, response.status);
6813
+ ({ error: error2 }) => {
6814
+ if (!isHTTPError(error2)) return error2;
6815
+ return parseApiError(error2.data, error2.response.status);
6325
6816
  }
6326
6817
  ]
6327
6818
  },
@@ -6495,6 +6986,117 @@ var AuditResource = class {
6495
6986
  return this.api.get("v1/audit", { searchParams: buildSearchParams(params) }).json();
6496
6987
  }
6497
6988
  };
6989
+ var MULTI_LEVEL_TLDS = /* @__PURE__ */ new Set([
6990
+ "co.uk",
6991
+ "org.uk",
6992
+ "ac.uk",
6993
+ "me.uk",
6994
+ "com.au",
6995
+ "net.au",
6996
+ "org.au",
6997
+ "co.nz",
6998
+ "net.nz",
6999
+ "org.nz",
7000
+ "co.jp",
7001
+ "or.jp",
7002
+ "ne.jp",
7003
+ "co.kr",
7004
+ "or.kr",
7005
+ "co.za",
7006
+ "org.za",
7007
+ "web.za",
7008
+ "co.in",
7009
+ "net.in",
7010
+ "org.in",
7011
+ "co.id",
7012
+ "or.id",
7013
+ "com.br",
7014
+ "net.br",
7015
+ "org.br",
7016
+ "com.mx",
7017
+ "org.mx",
7018
+ "com.ar",
7019
+ "org.ar",
7020
+ "com.cn",
7021
+ "net.cn",
7022
+ "org.cn",
7023
+ "com.tw",
7024
+ "org.tw",
7025
+ "com.sg",
7026
+ "org.sg",
7027
+ "com.ph",
7028
+ "org.ph",
7029
+ "com.tr",
7030
+ "org.tr",
7031
+ "com.ng",
7032
+ "org.ng"
7033
+ ]);
7034
+ var PUBLIC_HOSTING_SUFFIXES = /* @__PURE__ */ new Set([
7035
+ "github.io",
7036
+ "gitlab.io",
7037
+ "bitbucket.io",
7038
+ "vercel.app",
7039
+ "netlify.app",
7040
+ "netlify.com",
7041
+ "lovable.app",
7042
+ "lovableproject.com",
7043
+ "herokuapp.com",
7044
+ "appspot.com",
7045
+ "firebaseapp.com",
7046
+ "web.app",
7047
+ "azurewebsites.net",
7048
+ "cloudfront.net",
7049
+ "amazonaws.com",
7050
+ "replit.app",
7051
+ "repl.co",
7052
+ "readme.io",
7053
+ "readthedocs.io",
7054
+ "readthedocs.org",
7055
+ "statuspage.io",
7056
+ "uptimerobot.com",
7057
+ "myshopify.com",
7058
+ "webflow.io",
7059
+ "wixsite.com",
7060
+ "squarespace.com",
7061
+ "blogspot.com",
7062
+ "tumblr.com",
7063
+ "substack.com",
7064
+ "medium.com",
7065
+ "slack.com",
7066
+ "notion.site",
7067
+ "codesandbox.io",
7068
+ "stackblitz.io",
7069
+ "glitch.me",
7070
+ "ngrok.app",
7071
+ "ngrok.io",
7072
+ "ngrok-free.app"
7073
+ ]);
7074
+ var IPV4_RE = /^\d{1,3}(\.\d{1,3}){3}$/;
7075
+ function normalize(value) {
7076
+ return value.replace(/:\d+$/, "").replace(/\.+$/, "").toLowerCase();
7077
+ }
7078
+ function detectAssetType(value) {
7079
+ const host = normalize(value);
7080
+ if (IPV4_RE.test(host) || host.includes(":")) {
7081
+ return "ip";
7082
+ }
7083
+ const labels = host.split(".");
7084
+ if (labels.length <= 1) return "domain";
7085
+ const tld2 = labels.slice(-2).join(".");
7086
+ const registrableLabels = MULTI_LEVEL_TLDS.has(tld2) ? 3 : 2;
7087
+ return labels.length > registrableLabels ? "subdomain" : "domain";
7088
+ }
7089
+ function deriveParentDomain(value) {
7090
+ const host = normalize(value);
7091
+ if (detectAssetType(host) !== "subdomain") return null;
7092
+ const labels = host.split(".");
7093
+ const tld2 = labels.slice(-2).join(".");
7094
+ const registrableLabels = MULTI_LEVEL_TLDS.has(tld2) ? 3 : 2;
7095
+ return labels.slice(-registrableLabels).join(".");
7096
+ }
7097
+ function isPublicSuffix(value) {
7098
+ return PUBLIC_HOSTING_SUFFIXES.has(normalize(value));
7099
+ }
6498
7100
 
6499
7101
  // src/lib/output.ts
6500
7102
  var import_cli_table3 = __toESM(require_cli_table3(), 1);
@@ -7775,7 +8377,7 @@ Examples:
7775
8377
  }
7776
8378
 
7777
8379
  // src/commands/pentest.ts
7778
- var import_picocolors7 = __toESM(require_picocolors(), 1);
8380
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
7779
8381
 
7780
8382
  // src/lib/confirm.ts
7781
8383
  var import_picocolors3 = __toESM(require_picocolors(), 1);
@@ -7835,8 +8437,21 @@ ${import_picocolors3.default.dim("use of its services.")}
7835
8437
 
7836
8438
  // src/commands/schedule.ts
7837
8439
  var import_picocolors4 = __toESM(require_picocolors(), 1);
8440
+
8441
+ // src/lib/validate-mode.ts
8442
+ function validateScanMode(mode) {
8443
+ if (mode === "quick") {
8444
+ console.error("Quick mode is temporarily unavailable. Please use --mode full (the default).");
8445
+ process.exit(2);
8446
+ }
8447
+ if (mode !== "full") {
8448
+ console.error(`Invalid mode "${mode}". Must be: full`);
8449
+ process.exit(2);
8450
+ }
8451
+ }
8452
+
8453
+ // src/commands/schedule.ts
7838
8454
  var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
7839
- var VALID_MODES = ["quick", "full"];
7840
8455
  var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
7841
8456
  function extractTime(scheduledAt) {
7842
8457
  if (!scheduledAt) return "\u2014";
@@ -7934,19 +8549,16 @@ Examples:
7934
8549
  handleError(error2);
7935
8550
  }
7936
8551
  });
7937
- schedule.command("create").description("Create a scheduled pentest").requiredOption("--asset <id>", "Asset ID to scan").requiredOption("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode: quick or full", "full").option("--time <HH:MM>", "Time of day (24h format)", "09:00").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat) for weekly/biweekly").option("--day-of-month <1-31>", "Day of month for monthly").option("--timezone <tz>", "Timezone (e.g. America/New_York)", Intl.DateTimeFormat().resolvedOptions().timeZone).addHelpText("after", `
8552
+ schedule.command("create").description("Create a scheduled pentest").requiredOption("--asset <id>", "Asset ID to scan").requiredOption("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode (default: full)", "full").option("--time <HH:MM>", "Time of day (24h format)", "09:00").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat) for weekly/biweekly").option("--day-of-month <1-31>", "Day of month for monthly").option("--timezone <tz>", "Timezone (e.g. America/New_York)", Intl.DateTimeFormat().resolvedOptions().timeZone).addHelpText("after", `
7938
8553
  Examples:
7939
8554
  $ kinosec pentest schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
7940
- $ kinosec pentest schedule create --asset abc123 --pattern daily --mode quick --time 14:00
8555
+ $ kinosec pentest schedule create --asset abc123 --pattern daily --time 14:00
7941
8556
  $ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
7942
8557
  if (!VALID_PATTERNS.includes(options.pattern)) {
7943
8558
  console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
7944
8559
  process.exit(2);
7945
8560
  }
7946
- if (!VALID_MODES.includes(options.mode)) {
7947
- console.error(`Invalid mode "${options.mode}". Must be: ${VALID_MODES.join(", ")}`);
7948
- process.exit(2);
7949
- }
8561
+ validateScanMode(options.mode);
7950
8562
  if ((options.pattern === "weekly" || options.pattern === "biweekly") && options.dayOfWeek === void 0) {
7951
8563
  console.error(`--day-of-week is required for ${options.pattern} schedules.`);
7952
8564
  process.exit(2);
@@ -7984,14 +8596,17 @@ Manage: kinosec pentest schedule get ${s.id}`);
7984
8596
  handleError(error2);
7985
8597
  }
7986
8598
  });
7987
- schedule.command("update").description("Update a schedule").argument("<id>", "Schedule ID").option("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode: quick or full").option("--time <HH:MM>", "Time of day (24h format)").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat)").option("--day-of-month <1-31>", "Day of month").option("--timezone <tz>", "Timezone").addHelpText("after", `
8599
+ schedule.command("update").description("Update a schedule").argument("<id>", "Schedule ID").option("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode").option("--time <HH:MM>", "Time of day (24h format)").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat)").option("--day-of-month <1-31>", "Day of month").option("--timezone <tz>", "Timezone").addHelpText("after", `
7988
8600
  Examples:
7989
8601
  $ kinosec pentest schedule update abc123 --pattern daily --time 14:00
7990
- $ kinosec pentest schedule update abc123 --mode quick`).action(async (id, options) => {
8602
+ $ kinosec pentest schedule update abc123 --time 14:00`).action(async (id, options) => {
7991
8603
  const params = {};
7992
8604
  if (options.pattern) params.recurrence_pattern = options.pattern;
7993
8605
  if (options.name) params.name = options.name;
7994
- if (options.mode) params.scan_mode = options.mode;
8606
+ if (options.mode) {
8607
+ validateScanMode(options.mode);
8608
+ params.scan_mode = options.mode;
8609
+ }
7995
8610
  if (options.time) params.time_of_day = options.time;
7996
8611
  if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
7997
8612
  if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
@@ -8088,7 +8703,7 @@ function isUnicodeSupported() {
8088
8703
  return Boolean(env2.WT_SESSION) || Boolean(env2.TERMINUS_SUBLIME) || env2.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env2.TERMINAL_EMULATOR === "JetBrains-JediTerm";
8089
8704
  }
8090
8705
 
8091
- // ../../node_modules/.pnpm/ora@9.4.0/node_modules/ora/index.js
8706
+ // ../../node_modules/.pnpm/ora@9.4.1/node_modules/ora/index.js
8092
8707
  import process8 from "process";
8093
8708
  import { stripVTControlCharacters } from "util";
8094
8709
 
@@ -10823,7 +11438,7 @@ var StdinDiscarder = class {
10823
11438
  var stdinDiscarder = new StdinDiscarder();
10824
11439
  var stdin_discarder_default = Object.freeze(stdinDiscarder);
10825
11440
 
10826
- // ../../node_modules/.pnpm/ora@9.4.0/node_modules/ora/index.js
11441
+ // ../../node_modules/.pnpm/ora@9.4.1/node_modules/ora/index.js
10827
11442
  var RENDER_DEFERRAL_TIMEOUT = 200;
10828
11443
  var SYNCHRONIZED_OUTPUT_ENABLE = "\x1B[?2026h";
10829
11444
  var SYNCHRONIZED_OUTPUT_DISABLE = "\x1B[?2026l";
@@ -11202,7 +11817,7 @@ var Ora = class {
11202
11817
  return this;
11203
11818
  }
11204
11819
  start(text) {
11205
- if (text) {
11820
+ if (text !== void 0) {
11206
11821
  this.text = text;
11207
11822
  }
11208
11823
  if (this.isSilent) {
@@ -11563,105 +12178,344 @@ function printOnPremInstructions(result) {
11563
12178
  console.log(` ${sym2.warn} Token expires in 15 minutes.`);
11564
12179
  }
11565
12180
  }
11566
- async function waitForScoutConnection(client, profileId, options) {
11567
- const { timeoutMs, intervalMs, useJson } = options;
11568
- const tty3 = isTTY();
11569
- const loopStart = Date.now();
11570
- let spinner = null;
11571
- if (!useJson && tty3) {
11572
- spinner = ora({
11573
- text: "Waiting for Scout connection...",
11574
- color: "green",
11575
- isEnabled: true
11576
- }).start();
12181
+ async function waitForScoutConnection(client, profileId, options) {
12182
+ const { timeoutMs, intervalMs, useJson } = options;
12183
+ const tty3 = isTTY();
12184
+ const loopStart = Date.now();
12185
+ let spinner = null;
12186
+ if (!useJson && tty3) {
12187
+ spinner = ora({
12188
+ text: "Waiting for Scout connection...",
12189
+ color: "green",
12190
+ isEnabled: true
12191
+ }).start();
12192
+ }
12193
+ const cleanup = () => {
12194
+ if (spinner) spinner.stop();
12195
+ };
12196
+ const onSigint = () => {
12197
+ cleanup();
12198
+ console.log("\nWatch cancelled. Pentest is waiting for Scout connection.");
12199
+ process.exit(0);
12200
+ };
12201
+ process.once("SIGINT", onSigint);
12202
+ while (true) {
12203
+ const status = await client.scans.scoutStatus(profileId);
12204
+ if (useJson) {
12205
+ const elapsed = Math.round((Date.now() - loopStart) / 1e3);
12206
+ process.stdout.write(
12207
+ JSON.stringify({ ...status, elapsed_seconds: elapsed }) + "\n"
12208
+ );
12209
+ }
12210
+ switch (status.status) {
12211
+ case "pending":
12212
+ if (spinner) spinner.text = import_picocolors6.default.green("Waiting for Scout bootstrap...");
12213
+ break;
12214
+ case "bootstrapped":
12215
+ if (spinner) spinner.text = import_picocolors6.default.green("Scout bootstrapped, establishing connection...");
12216
+ break;
12217
+ case "connected": {
12218
+ cleanup();
12219
+ process.removeListener("SIGINT", onSigint);
12220
+ if (!useJson) {
12221
+ const icon = tty3 ? import_picocolors6.default.green(sym2.success) : sym2.success;
12222
+ console.log(`${icon} Scout connected`);
12223
+ if (status.hostname) {
12224
+ console.log(` Hostname: ${status.hostname}`);
12225
+ }
12226
+ if (status.tools.length > 0) {
12227
+ console.log(` Tools: ${status.tools.join(", ")}`);
12228
+ }
12229
+ }
12230
+ return;
12231
+ }
12232
+ case "expired": {
12233
+ cleanup();
12234
+ process.removeListener("SIGINT", onSigint);
12235
+ const icon = tty3 ? import_picocolors6.default.red(sym2.fail) : sym2.fail;
12236
+ console.error(`
12237
+ ${icon} Scout bootstrap token expired.`);
12238
+ console.error("Start a new pentest to get a fresh token.");
12239
+ process.exit(1);
12240
+ }
12241
+ case "disconnected": {
12242
+ cleanup();
12243
+ process.removeListener("SIGINT", onSigint);
12244
+ const icon = tty3 ? import_picocolors6.default.red(sym2.fail) : sym2.fail;
12245
+ console.error(`
12246
+ ${icon} Scout disconnected unexpectedly.`);
12247
+ console.error(`Check status: kinosec pentest get ${profileId}`);
12248
+ process.exit(1);
12249
+ }
12250
+ }
12251
+ if (Date.now() - loopStart > timeoutMs) {
12252
+ cleanup();
12253
+ process.removeListener("SIGINT", onSigint);
12254
+ const secs = Math.round(timeoutMs / 1e3);
12255
+ console.error(`
12256
+ Timeout: Scout did not connect within ${secs}s.`);
12257
+ console.error("The bootstrap token may have expired. Start a new pentest.");
12258
+ process.exit(3);
12259
+ }
12260
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
12261
+ }
12262
+ }
12263
+
12264
+ // src/lib/scope.ts
12265
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
12266
+
12267
+ // src/lib/prompt.ts
12268
+ import { createInterface as createInterface2 } from "readline";
12269
+ async function promptMasked(label) {
12270
+ if (!isTTY() || !process.stdin.isTTY) {
12271
+ console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
12272
+ process.exit(2);
12273
+ }
12274
+ process.stderr.write(`${label}: `);
12275
+ return new Promise((resolve, reject) => {
12276
+ const chars = [];
12277
+ process.stdin.setRawMode(true);
12278
+ process.stdin.resume();
12279
+ process.stdin.setEncoding("utf-8");
12280
+ const onData = (chunk) => {
12281
+ for (const ch of chunk) {
12282
+ if (ch === "\r" || ch === "\n") {
12283
+ process.stdin.setRawMode(false);
12284
+ process.stdin.pause();
12285
+ process.stdin.removeListener("data", onData);
12286
+ process.stderr.write("\n");
12287
+ resolve(chars.join(""));
12288
+ return;
12289
+ } else if (ch === "") {
12290
+ process.stdin.setRawMode(false);
12291
+ process.stdin.pause();
12292
+ process.stdin.removeListener("data", onData);
12293
+ process.stderr.write("\n");
12294
+ reject(new Error("Cancelled"));
12295
+ return;
12296
+ } else if (ch === "\x7F" || ch === "\b") {
12297
+ if (chars.length > 0) {
12298
+ chars.pop();
12299
+ process.stderr.write("\b \b");
12300
+ }
12301
+ } else if (ch >= " ") {
12302
+ chars.push(ch);
12303
+ process.stderr.write("*");
12304
+ }
12305
+ }
12306
+ };
12307
+ process.stdin.on("data", onData);
12308
+ });
12309
+ }
12310
+ async function promptText(label) {
12311
+ if (!isTTY() || !process.stdin.isTTY) {
12312
+ console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
12313
+ process.exit(2);
12314
+ }
12315
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
12316
+ const answer = await new Promise((resolve) => {
12317
+ rl.question(`${label}: `, resolve);
12318
+ });
12319
+ rl.close();
12320
+ return answer;
12321
+ }
12322
+ async function promptSelect(message, options) {
12323
+ if (!isTTY() || !process.stdin.isTTY) {
12324
+ console.error("Cannot show interactive menu in non-interactive mode. Use flags instead.");
12325
+ process.exit(2);
12326
+ }
12327
+ console.error(`
12328
+ ${message}`);
12329
+ for (let i = 0; i < options.length; i++) {
12330
+ console.error(` ${i + 1}. ${options[i].label} \u2014 ${options[i].description}`);
12331
+ }
12332
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
12333
+ const answer = await new Promise((resolve) => {
12334
+ rl.question("> ", resolve);
12335
+ });
12336
+ rl.close();
12337
+ const index = parseInt(answer, 10) - 1;
12338
+ if (isNaN(index) || index < 0 || index >= options.length) {
12339
+ console.error("Invalid selection.");
12340
+ process.exit(2);
12341
+ }
12342
+ return options[index].value;
12343
+ }
12344
+
12345
+ // src/lib/scope.ts
12346
+ var HOSTNAME_RE = /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i;
12347
+ var MAX_SCOPE_HOSTS = 50;
12348
+ function parseScopeHosts(input) {
12349
+ return input.split(",").map((h) => h.trim()).filter(Boolean);
12350
+ }
12351
+ function validateScopeHosts(hosts) {
12352
+ for (const host of hosts) {
12353
+ if (!HOSTNAME_RE.test(host)) {
12354
+ console.error(`Error: Invalid hostname "${host}". Must be a valid domain (e.g., api.example.com).`);
12355
+ process.exit(2);
12356
+ }
12357
+ }
12358
+ const seen = /* @__PURE__ */ new Set();
12359
+ for (const host of hosts) {
12360
+ const lower = host.toLowerCase();
12361
+ if (seen.has(lower)) {
12362
+ console.error(`Error: Duplicate hostname "${host}" in list.`);
12363
+ process.exit(2);
12364
+ }
12365
+ seen.add(lower);
12366
+ }
12367
+ }
12368
+ function validateScopeHostOwnership(hosts, assetValue) {
12369
+ const suffix = `.${assetValue.toLowerCase()}`;
12370
+ for (const host of hosts) {
12371
+ if (!host.toLowerCase().endsWith(suffix)) {
12372
+ console.error(`Error: ${host} is not a subdomain of ${assetValue}.`);
12373
+ process.exit(2);
12374
+ }
12375
+ }
12376
+ }
12377
+ function validateScopeFlags(options) {
12378
+ if (options.include && options.exclude) {
12379
+ console.error("Error: --include and --exclude cannot be used together. Use one or the other.");
12380
+ process.exit(2);
12381
+ }
12382
+ if (options.scopeClear && (options.include || options.exclude)) {
12383
+ console.error("Error: --clear cannot be combined with --include or --exclude.");
12384
+ process.exit(2);
12385
+ }
12386
+ }
12387
+ function validateTotalHostCount(existingCount, newCount) {
12388
+ const total = existingCount + newCount;
12389
+ if (total > MAX_SCOPE_HOSTS) {
12390
+ console.error(`Error: Maximum ${MAX_SCOPE_HOSTS} hosts allowed. You provided ${total}.`);
12391
+ process.exit(2);
11577
12392
  }
11578
- const cleanup = () => {
11579
- if (spinner) spinner.stop();
11580
- };
11581
- const onSigint = () => {
11582
- cleanup();
11583
- console.log("\nWatch cancelled. Pentest is waiting for Scout connection.");
11584
- process.exit(0);
11585
- };
11586
- process.once("SIGINT", onSigint);
11587
- while (true) {
11588
- const status = await client.scans.scoutStatus(profileId);
11589
- if (useJson) {
11590
- const elapsed = Math.round((Date.now() - loopStart) / 1e3);
11591
- process.stdout.write(
11592
- JSON.stringify({ ...status, elapsed_seconds: elapsed }) + "\n"
11593
- );
12393
+ }
12394
+ function displayScope(asset) {
12395
+ if (!asset.scope_filter || !asset.scope_hosts?.length) {
12396
+ console.error(`Scope: ${isTTY() ? import_picocolors7.default.dim("all subdomains") : "all subdomains"}`);
12397
+ return;
12398
+ }
12399
+ const label = asset.scope_filter === "include" ? "include only" : "exclude";
12400
+ const hosts = asset.scope_hosts.join(", ");
12401
+ console.error(`Scope: ${label} \u2014 ${hosts}`);
12402
+ }
12403
+ async function resolveScopeUpdate(asset, mode, newHosts) {
12404
+ const existingMode = asset.scope_filter;
12405
+ const existingHosts = asset.scope_hosts ?? [];
12406
+ if (!existingMode || existingHosts.length === 0) {
12407
+ validateScopeHosts(newHosts);
12408
+ if (newHosts.length > MAX_SCOPE_HOSTS) {
12409
+ console.error(`Error: Maximum ${MAX_SCOPE_HOSTS} hosts allowed. You provided ${newHosts.length}.`);
12410
+ process.exit(2);
11594
12411
  }
11595
- switch (status.status) {
11596
- case "pending":
11597
- if (spinner) spinner.text = import_picocolors6.default.green("Waiting for Scout bootstrap...");
11598
- break;
11599
- case "bootstrapped":
11600
- if (spinner) spinner.text = import_picocolors6.default.green("Scout bootstrapped, establishing connection...");
11601
- break;
11602
- case "connected": {
11603
- cleanup();
11604
- process.removeListener("SIGINT", onSigint);
11605
- if (!useJson) {
11606
- const icon = tty3 ? import_picocolors6.default.green(sym2.success) : sym2.success;
11607
- console.log(`${icon} Scout connected`);
11608
- if (status.hostname) {
11609
- console.log(` Hostname: ${status.hostname}`);
11610
- }
11611
- if (status.tools.length > 0) {
11612
- console.log(` Tools: ${status.tools.join(", ")}`);
11613
- }
11614
- }
11615
- return;
11616
- }
11617
- case "expired": {
11618
- cleanup();
11619
- process.removeListener("SIGINT", onSigint);
11620
- const icon = tty3 ? import_picocolors6.default.red(sym2.fail) : sym2.fail;
11621
- console.error(`
11622
- ${icon} Scout bootstrap token expired.`);
11623
- console.error("Start a new pentest to get a fresh token.");
11624
- process.exit(1);
11625
- }
11626
- case "disconnected": {
11627
- cleanup();
11628
- process.removeListener("SIGINT", onSigint);
11629
- const icon = tty3 ? import_picocolors6.default.red(sym2.fail) : sym2.fail;
11630
- console.error(`
11631
- ${icon} Scout disconnected unexpectedly.`);
11632
- console.error(`Check status: kinosec pentest get ${profileId}`);
11633
- process.exit(1);
11634
- }
12412
+ return { scope_filter: mode, scope_hosts: newHosts };
12413
+ }
12414
+ if (existingMode !== mode) {
12415
+ console.error(`
12416
+ Existing ${existingMode} scope:`);
12417
+ for (const host of existingHosts) {
12418
+ console.error(` - ${host}`);
11635
12419
  }
11636
- if (Date.now() - loopStart > timeoutMs) {
11637
- cleanup();
11638
- process.removeListener("SIGINT", onSigint);
11639
- const secs = Math.round(timeoutMs / 1e3);
11640
- console.error(`
11641
- Timeout: Scout did not connect within ${secs}s.`);
11642
- console.error("The bootstrap token may have expired. Start a new pentest.");
11643
- process.exit(3);
12420
+ const answer2 = await promptSelect(
12421
+ `You have an existing ${existingMode} scope. Using --${mode} will replace it. Continue?`,
12422
+ [
12423
+ { value: "switch", label: `Yes, switch to ${mode}`, description: `Replace ${existingMode} list with new ${mode} list` },
12424
+ { value: "cancel", label: "Cancel", description: "Keep existing scope" }
12425
+ ]
12426
+ );
12427
+ if (answer2 === "cancel") {
12428
+ console.error("Cancelled.");
12429
+ return null;
11644
12430
  }
11645
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
12431
+ validateScopeHosts(newHosts);
12432
+ if (newHosts.length > MAX_SCOPE_HOSTS) {
12433
+ console.error(`Error: Maximum ${MAX_SCOPE_HOSTS} hosts allowed. You provided ${newHosts.length}.`);
12434
+ process.exit(2);
12435
+ }
12436
+ return { scope_filter: mode, scope_hosts: newHosts };
12437
+ }
12438
+ console.error(`
12439
+ Existing ${existingMode} scope:`);
12440
+ for (const host of existingHosts) {
12441
+ console.error(` - ${host}`);
12442
+ }
12443
+ const answer = await promptSelect(
12444
+ `You have existing subdomains in your ${mode} list. What would you like to do?`,
12445
+ [
12446
+ { value: "add", label: "Add to existing list", description: "Append new hosts to the current list" },
12447
+ { value: "replace", label: "Replace with new list", description: "Remove existing hosts and use only the new ones" }
12448
+ ]
12449
+ );
12450
+ if (answer === "add") {
12451
+ validateScopeHosts(newHosts);
12452
+ const existingLower = new Set(existingHosts.map((h) => h.toLowerCase()));
12453
+ const deduped = newHosts.filter((h) => !existingLower.has(h.toLowerCase()));
12454
+ if (deduped.length === 0) {
12455
+ console.error("All provided hosts are already in the list.");
12456
+ return { scope_filter: mode, scope_hosts: existingHosts };
12457
+ }
12458
+ validateScopeHosts(deduped);
12459
+ validateTotalHostCount(existingHosts.length, deduped.length);
12460
+ const merged = [...existingHosts, ...deduped];
12461
+ return { scope_filter: mode, scope_hosts: merged };
12462
+ }
12463
+ validateScopeHosts(newHosts);
12464
+ if (newHosts.length > MAX_SCOPE_HOSTS) {
12465
+ console.error(`Error: Maximum ${MAX_SCOPE_HOSTS} hosts allowed. You provided ${newHosts.length}.`);
12466
+ process.exit(2);
11646
12467
  }
12468
+ return { scope_filter: mode, scope_hosts: newHosts };
11647
12469
  }
11648
12470
 
11649
- // src/commands/pentest.ts
11650
- function stripProtocol(input) {
11651
- const match = input.match(/^https?:\/\/(.+)/i);
11652
- if (match) return { domain: match[1].replace(/\/+$/, ""), warned: true };
11653
- return { domain: input, warned: false };
11654
- }
11655
- function isValidDomain(value) {
11656
- return value.includes(".") && !/\s/.test(value);
12471
+ // src/lib/parse-target.ts
12472
+ function parseTarget(input) {
12473
+ const warnings = [];
12474
+ let value = input.trim();
12475
+ const protocolMatch = value.match(/^https?:\/\/(.+)/i);
12476
+ if (protocolMatch) {
12477
+ value = protocolMatch[1];
12478
+ }
12479
+ const slashIndex = value.indexOf("/");
12480
+ if (slashIndex !== -1) {
12481
+ value = value.slice(0, slashIndex);
12482
+ }
12483
+ value = value.replace(/\.+$/, "").toLowerCase();
12484
+ if (protocolMatch) {
12485
+ warnings.push(`Protocol stripped from input. Using "${value}".`);
12486
+ }
12487
+ if (/\s/.test(value) || !value.includes(".")) {
12488
+ throw new Error(
12489
+ `Invalid target: "${input}". Provide a domain like example.com or an IP like 192.168.1.1.`
12490
+ );
12491
+ }
12492
+ const assetType = detectAssetType(value);
12493
+ const parentDomain = deriveParentDomain(value);
12494
+ const isPubSuffix = isPublicSuffix(value);
12495
+ if (isPubSuffix) {
12496
+ warnings.push(
12497
+ `${value} is a public hosting suffix. You probably want your specific subdomain (e.g. myapp.${value}).`
12498
+ );
12499
+ }
12500
+ return {
12501
+ hostname: value,
12502
+ assetType,
12503
+ parentDomain,
12504
+ isPublicSuffix: isPubSuffix,
12505
+ warnings
12506
+ };
11657
12507
  }
12508
+
12509
+ // src/commands/pentest.ts
11658
12510
  function registerPentestCommand(program3) {
11659
- const pentest = program3.command("pentest").description("Run and manage pentests").option("-t, --target <domain>", "Target domain to pentest").option("--profile <id>", "Pentest profile ID (alternative to --target)").option("--mode <mode>", "Pentest mode: quick or full", "full").option("-y, --yes", "Accept authorization terms without prompting").option("--no-watch", "Skip auto-watch after starting the pentest").option("--deployment <type>", "Deployment type: cloud or on-prem", "cloud").addHelpText("after", `
12511
+ const pentest = program3.command("pentest").description("Run and manage pentests").option("-t, --target <domain>", "Target domain to pentest").option("--profile <id>", "Pentest profile ID (alternative to --target)").option("--mode <mode>", "Pentest mode", "full").option("-y, --yes", "Accept authorization terms without prompting").option("--no-watch", "Skip auto-watch after starting the pentest").option("--deployment <type>", "Deployment type: cloud or on-prem", "cloud").option("--include <hosts>", "Include only these subdomains (comma-separated)").option("--exclude <hosts>", "Exclude these subdomains (comma-separated)").option("--scope <action>", "Scope action: clear").addHelpText("after", `
11660
12512
  Examples:
11661
12513
  $ kinosec pentest -t delikado.org
11662
- $ kinosec pentest --target delikado.org --mode quick
11663
12514
  $ kinosec pentest --profile abc123
11664
- $ kinosec pentest -t internal.corp --deployment on-prem`).action(async (options) => {
12515
+ $ kinosec pentest -t internal.corp --deployment on-prem
12516
+ $ kinosec pentest -t example.com --include api.example.com,app.example.com
12517
+ $ kinosec pentest -t example.com --exclude staging.example.com
12518
+ $ kinosec pentest -t example.com --scope clear`).action(async (options) => {
11665
12519
  if (!options.target && !options.profile) {
11666
12520
  console.error("Provide --target <domain> or --profile <id>.");
11667
12521
  process.exit(2);
@@ -11673,6 +12527,21 @@ Examples:
11673
12527
  console.error("Invalid deployment type. Must be: cloud or on-prem.");
11674
12528
  process.exit(2);
11675
12529
  }
12530
+ validateScanMode(options.mode);
12531
+ const scopeClear = options.scope === "clear";
12532
+ if (options.scope && options.scope !== "clear") {
12533
+ console.error("Invalid --scope value. Must be: clear");
12534
+ process.exit(2);
12535
+ }
12536
+ validateScopeFlags({
12537
+ include: options.include,
12538
+ exclude: options.exclude,
12539
+ scopeClear
12540
+ });
12541
+ if (options.profile && (options.include || options.exclude || scopeClear)) {
12542
+ console.error("Error: Scope flags are only supported with --target, not --profile.");
12543
+ process.exit(2);
12544
+ }
11676
12545
  try {
11677
12546
  const opts = program3.opts();
11678
12547
  const { token, apiUrl } = resolveToken(opts);
@@ -11684,31 +12553,33 @@ Examples:
11684
12553
  targetDisplay = detail.domain_or_ip;
11685
12554
  params = { scan_profile_id: options.profile };
11686
12555
  } else {
11687
- const { domain, warned } = stripProtocol(options.target);
11688
- if (warned) {
11689
- console.error(`Warning: protocol stripped. Using "${domain}".`);
11690
- }
11691
- if (!isValidDomain(domain)) {
11692
- console.error(`Invalid domain format: "${domain}"`);
11693
- console.error("Provide a domain like delikado.org or juice.delikado.org.");
11694
- process.exit(2);
12556
+ const parsed = parseTarget(options.target);
12557
+ for (const warning2 of parsed.warnings) {
12558
+ console.error(`Warning: ${warning2}`);
12559
+ }
12560
+ if (parsed.isPublicSuffix) {
12561
+ console.error(
12562
+ `
12563
+ ${parsed.hostname} is a shared hosting domain. The server will likely reject this.`
12564
+ );
11695
12565
  }
11696
12566
  let asset;
11697
12567
  try {
11698
- asset = await client.assets.lookup(domain);
12568
+ asset = await client.assets.lookup(parsed.hostname);
11699
12569
  } catch (err) {
11700
12570
  if (err instanceof ApiError && err.status === 404) {
11701
12571
  asset = await client.assets.create({
11702
- asset_type: "domain",
11703
- asset_value: domain,
12572
+ asset_type: parsed.assetType,
12573
+ asset_value: parsed.hostname,
12574
+ parent_domain: parsed.parentDomain,
11704
12575
  verification_method: "dns"
11705
12576
  });
11706
- console.error(`Asset created for ${domain}.`);
12577
+ console.error(`Asset created for ${parsed.hostname}.`);
11707
12578
  } else {
11708
12579
  throw err;
11709
12580
  }
11710
12581
  }
11711
- targetDisplay = domain;
12582
+ targetDisplay = parsed.hostname;
11712
12583
  const assetParams = {
11713
12584
  asset_id: asset.id,
11714
12585
  scan_mode: options.mode
@@ -11717,6 +12588,40 @@ Examples:
11717
12588
  assetParams.deployment_type = options.deployment;
11718
12589
  }
11719
12590
  params = assetParams;
12591
+ if (scopeClear || options.include || options.exclude) {
12592
+ if (asset.asset_type !== "domain") {
12593
+ const hint = parsed.parentDomain ? `
12594
+ Target the parent domain instead:
12595
+ kinosec pentest -t ${parsed.parentDomain} --${options.include ? "include" : "exclude"} ${parsed.hostname}` : "";
12596
+ console.error(
12597
+ `Error: Scope filtering is only available for domain assets. ${parsed.hostname} is a ${asset.asset_type}.${hint}`
12598
+ );
12599
+ process.exit(2);
12600
+ }
12601
+ }
12602
+ if (scopeClear) {
12603
+ await client.assets.update(asset.id, {
12604
+ scope_filter: null,
12605
+ scope_hosts: null
12606
+ });
12607
+ console.error("Scope settings cleared. Scans will now cover all subdomains by default.");
12608
+ asset = await client.assets.get(asset.id);
12609
+ } else if (options.include || options.exclude) {
12610
+ const mode = options.include ? "include" : "exclude";
12611
+ const rawHosts = parseScopeHosts(options.include ?? options.exclude);
12612
+ validateScopeHosts(rawHosts);
12613
+ validateScopeHostOwnership(rawHosts, asset.asset_value);
12614
+ const scopeResult = await resolveScopeUpdate(asset, mode, rawHosts);
12615
+ if (!scopeResult) {
12616
+ process.exit(0);
12617
+ }
12618
+ await client.assets.update(asset.id, {
12619
+ scope_filter: scopeResult.scope_filter,
12620
+ scope_hosts: scopeResult.scope_hosts
12621
+ });
12622
+ asset = await client.assets.get(asset.id);
12623
+ }
12624
+ displayScope(asset);
11720
12625
  }
11721
12626
  await confirmAuthorization(targetDisplay, Boolean(options.yes));
11722
12627
  const result = await client.scans.create(params);
@@ -11731,7 +12636,7 @@ Examples:
11731
12636
  if (isOnPrem) {
11732
12637
  printOnPremInstructions(result);
11733
12638
  if (options.watch && isTTY()) {
11734
- const divider2 = import_picocolors7.default.dim("\u2500".repeat(37));
12639
+ const divider2 = import_picocolors8.default.dim("\u2500".repeat(37));
11735
12640
  console.log(`
11736
12641
  ${divider2}
11737
12642
  `);
@@ -11758,7 +12663,7 @@ Watch connection: kinosec pentest watch ${result.scan_profile_id}`);
11758
12663
  Watch progress: kinosec pentest watch ${result.scan_profile_id}`);
11759
12664
  return;
11760
12665
  }
11761
- const divider = isTTY() ? import_picocolors7.default.dim("\u2500".repeat(37)) : "-".repeat(37);
12666
+ const divider = isTTY() ? import_picocolors8.default.dim("\u2500".repeat(37)) : "-".repeat(37);
11762
12667
  console.log(`
11763
12668
  ${divider}
11764
12669
  `);
@@ -11769,6 +12674,9 @@ ${divider}
11769
12674
  showFindingsSummary: true
11770
12675
  });
11771
12676
  } catch (error2) {
12677
+ if (error2 instanceof Error && error2.message.startsWith("process.exit(")) {
12678
+ throw error2;
12679
+ }
11772
12680
  handleError(error2);
11773
12681
  }
11774
12682
  });
@@ -11804,7 +12712,7 @@ Examples:
11804
12712
  ])
11805
12713
  );
11806
12714
  if (result.next_cursor) {
11807
- console.log(import_picocolors7.default.dim(`
12715
+ console.log(import_picocolors8.default.dim(`
11808
12716
  More results available. Use --cursor ${result.next_cursor}`));
11809
12717
  }
11810
12718
  } catch (error2) {
@@ -11896,10 +12804,10 @@ Examples:
11896
12804
  }
11897
12805
 
11898
12806
  // src/commands/asset.ts
11899
- var import_picocolors9 = __toESM(require_picocolors(), 1);
12807
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
11900
12808
 
11901
12809
  // src/commands/asset-auth.ts
11902
- var import_picocolors8 = __toESM(require_picocolors(), 1);
12810
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
11903
12811
 
11904
12812
  // src/lib/resolve-asset.ts
11905
12813
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
@@ -11925,84 +12833,6 @@ or use pentest which auto-creates the asset:
11925
12833
  }
11926
12834
  }
11927
12835
 
11928
- // src/lib/prompt.ts
11929
- import { createInterface as createInterface2 } from "readline";
11930
- async function promptMasked(label) {
11931
- if (!isTTY() || !process.stdin.isTTY) {
11932
- console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
11933
- process.exit(2);
11934
- }
11935
- process.stderr.write(`${label}: `);
11936
- return new Promise((resolve, reject) => {
11937
- const chars = [];
11938
- process.stdin.setRawMode(true);
11939
- process.stdin.resume();
11940
- process.stdin.setEncoding("utf-8");
11941
- const onData = (chunk) => {
11942
- for (const ch of chunk) {
11943
- if (ch === "\r" || ch === "\n") {
11944
- process.stdin.setRawMode(false);
11945
- process.stdin.pause();
11946
- process.stdin.removeListener("data", onData);
11947
- process.stderr.write("\n");
11948
- resolve(chars.join(""));
11949
- return;
11950
- } else if (ch === "") {
11951
- process.stdin.setRawMode(false);
11952
- process.stdin.pause();
11953
- process.stdin.removeListener("data", onData);
11954
- process.stderr.write("\n");
11955
- reject(new Error("Cancelled"));
11956
- return;
11957
- } else if (ch === "\x7F" || ch === "\b") {
11958
- if (chars.length > 0) {
11959
- chars.pop();
11960
- process.stderr.write("\b \b");
11961
- }
11962
- } else if (ch >= " ") {
11963
- chars.push(ch);
11964
- process.stderr.write("*");
11965
- }
11966
- }
11967
- };
11968
- process.stdin.on("data", onData);
11969
- });
11970
- }
11971
- async function promptText(label) {
11972
- if (!isTTY() || !process.stdin.isTTY) {
11973
- console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
11974
- process.exit(2);
11975
- }
11976
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
11977
- const answer = await new Promise((resolve) => {
11978
- rl.question(`${label}: `, resolve);
11979
- });
11980
- rl.close();
11981
- return answer;
11982
- }
11983
- async function promptSelect(message, options) {
11984
- if (!isTTY() || !process.stdin.isTTY) {
11985
- console.error("Cannot show interactive menu in non-interactive mode. Use flags instead.");
11986
- process.exit(2);
11987
- }
11988
- console.error(`
11989
- ${message}`);
11990
- for (let i = 0; i < options.length; i++) {
11991
- console.error(` ${i + 1}. ${options[i].label} \u2014 ${options[i].description}`);
11992
- }
11993
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
11994
- const answer = await new Promise((resolve) => {
11995
- rl.question("> ", resolve);
11996
- });
11997
- rl.close();
11998
- const index = parseInt(answer, 10) - 1;
11999
- if (isNaN(index) || index < 0 || index >= options.length) {
12000
- console.error("Invalid selection.");
12001
- process.exit(2);
12002
- }
12003
- return options[index].value;
12004
- }
12005
-
12006
12836
  // src/commands/asset-auth.ts
12007
12837
  var VALID_AUTH_TYPES = ["bearer", "basic", "apikey", "session", "cookie"];
12008
12838
  var AUTH_TYPE_OPTIONS = [
@@ -12156,7 +12986,7 @@ Examples:
12156
12986
  }
12157
12987
  if (!creds.has_credentials) {
12158
12988
  console.log(`Asset: ${asset.asset_value}`);
12159
- console.log(`Auth Type: ${import_picocolors8.default.dim("(none)")}`);
12989
+ console.log(`Auth Type: ${import_picocolors9.default.dim("(none)")}`);
12160
12990
  console.log(`Has Credentials: no`);
12161
12991
  return;
12162
12992
  }
@@ -12177,9 +13007,9 @@ Examples:
12177
13007
  const hasAny = ta.has_username || ta.has_cookie || ta.has_bearer_token || ta.has_apikey;
12178
13008
  if (hasAny) {
12179
13009
  const detail = ta.has_username ? "username" : ta.has_bearer_token ? "bearer token" : ta.has_cookie ? "cookie" : "API key";
12180
- console.log(`Target Account: ${import_picocolors8.default.green("configured")} (${detail})`);
13010
+ console.log(`Target Account: ${import_picocolors9.default.green("configured")} (${detail})`);
12181
13011
  } else {
12182
- console.log(`Target Account: ${import_picocolors8.default.dim("not configured")}`);
13012
+ console.log(`Target Account: ${import_picocolors9.default.dim("not configured")}`);
12183
13013
  }
12184
13014
  }
12185
13015
  } catch (error2) {
@@ -12320,17 +13150,18 @@ Examples:
12320
13150
  return;
12321
13151
  }
12322
13152
  printTable(
12323
- ["ID", "Value", "Type", "Status", "Created"],
13153
+ ["ID", "Value", "Type", "Status", "Scope", "Created"],
12324
13154
  result.items.map((a) => [
12325
13155
  a.id,
12326
13156
  truncate(a.asset_value, 40),
12327
13157
  a.asset_type,
12328
13158
  a.verification_status,
13159
+ a.scope_filter ?? "all",
12329
13160
  new Date(a.created_at).toLocaleDateString()
12330
13161
  ])
12331
13162
  );
12332
13163
  if (result.next_cursor) {
12333
- console.log(import_picocolors9.default.dim(`
13164
+ console.log(import_picocolors10.default.dim(`
12334
13165
  More results available. Use --cursor ${result.next_cursor}`));
12335
13166
  }
12336
13167
  } catch (error2) {
@@ -12350,18 +13181,24 @@ Examples:
12350
13181
  printJson(detail);
12351
13182
  return;
12352
13183
  }
12353
- printTable(
12354
- ["Field", "Value"],
12355
- [
12356
- ["ID", detail.id],
12357
- ["Value", detail.asset_value],
12358
- ["Type", detail.asset_type],
12359
- ["Status", detail.verification_status],
12360
- ["Parent", detail.parent_domain ?? "(none)"],
12361
- ["Created", detail.created_at],
12362
- ["Updated", detail.updated_at]
12363
- ]
13184
+ const rows = [
13185
+ ["ID", detail.id],
13186
+ ["Value", detail.asset_value],
13187
+ ["Type", detail.asset_type],
13188
+ ["Status", detail.verification_status],
13189
+ ["Parent", detail.parent_domain ?? "(none)"]
13190
+ ];
13191
+ if (detail.asset_type === "domain") {
13192
+ rows.push(["Scope", detail.scope_filter ?? "all subdomains"]);
13193
+ if (detail.scope_hosts?.length) {
13194
+ rows.push(["Hosts", detail.scope_hosts.join(", ")]);
13195
+ }
13196
+ }
13197
+ rows.push(
13198
+ ["Created", detail.created_at],
13199
+ ["Updated", detail.updated_at]
12364
13200
  );
13201
+ printTable(["Field", "Value"], rows);
12365
13202
  } catch (error2) {
12366
13203
  handleError(error2);
12367
13204
  }
@@ -12374,11 +13211,18 @@ Examples:
12374
13211
  const opts = program3.opts();
12375
13212
  const { token, apiUrl } = resolveToken(opts);
12376
13213
  const client = new KinoSecClient({ apiUrl, token });
13214
+ const detected = detectAssetType(options.value);
13215
+ if (detected !== options.type) {
13216
+ console.error(
13217
+ `Warning: ${options.value} looks like a ${detected}, not a ${options.type}. Continuing with your specified type.`
13218
+ );
13219
+ }
13220
+ const parentDomain = options.parent ?? (options.type === "subdomain" ? deriveParentDomain(options.value) : null);
12377
13221
  const result = await client.assets.create({
12378
13222
  asset_type: options.type,
12379
13223
  asset_value: options.value,
12380
13224
  verification_method: options.verifyMethod,
12381
- parent_domain: options.parent ?? null
13225
+ parent_domain: parentDomain
12382
13226
  });
12383
13227
  if (opts.json || opts.output === "json") {
12384
13228
  printJson(result);
@@ -12438,11 +13282,85 @@ Examples:
12438
13282
  handleError(error2);
12439
13283
  }
12440
13284
  });
13285
+ asset.command("scope").description("View or configure subdomain scope for an asset").argument("<id>", "Asset ID or domain").option("--include <hosts>", "Include only these subdomains (comma-separated)").option("--exclude <hosts>", "Exclude these subdomains (comma-separated)").option("--clear", "Clear scope settings").addHelpText("after", `
13286
+ Examples:
13287
+ $ kinosec asset scope abc123
13288
+ $ kinosec asset scope example.com --include api.example.com,app.example.com
13289
+ $ kinosec asset scope abc123 --exclude staging.example.com
13290
+ $ kinosec asset scope abc123 --clear`).action(async (id, options) => {
13291
+ try {
13292
+ validateScopeFlags({
13293
+ include: options.include,
13294
+ exclude: options.exclude,
13295
+ scopeClear: options.clear
13296
+ });
13297
+ const opts = program3.opts();
13298
+ const { token, apiUrl } = resolveToken(opts);
13299
+ const client = new KinoSecClient({ apiUrl, token });
13300
+ const asset2 = await resolveAsset(client, id);
13301
+ if (asset2.asset_type !== "domain") {
13302
+ console.error("Error: Scope filtering is only available for domain assets.");
13303
+ process.exit(2);
13304
+ }
13305
+ if (!options.include && !options.exclude && !options.clear) {
13306
+ if (opts.json || opts.output === "json") {
13307
+ printJson({
13308
+ scope_filter: asset2.scope_filter,
13309
+ scope_hosts: asset2.scope_hosts
13310
+ });
13311
+ return;
13312
+ }
13313
+ if (!asset2.scope_filter || !asset2.scope_hosts?.length) {
13314
+ console.log("Scope: all subdomains");
13315
+ } else {
13316
+ console.log(`Scope: ${asset2.scope_filter}`);
13317
+ console.log("Hosts:");
13318
+ for (const host of asset2.scope_hosts) {
13319
+ console.log(` - ${host}`);
13320
+ }
13321
+ }
13322
+ return;
13323
+ }
13324
+ if (options.clear) {
13325
+ await client.assets.update(asset2.id, {
13326
+ scope_filter: null,
13327
+ scope_hosts: null
13328
+ });
13329
+ if (opts.json || opts.output === "json") {
13330
+ printJson({ scope_filter: null, scope_hosts: null });
13331
+ return;
13332
+ }
13333
+ printSuccess("Scope settings cleared. Scans will now cover all subdomains by default.");
13334
+ return;
13335
+ }
13336
+ const mode = options.include ? "include" : "exclude";
13337
+ const rawHosts = parseScopeHosts(options.include ?? options.exclude);
13338
+ validateScopeHosts(rawHosts);
13339
+ validateScopeHostOwnership(rawHosts, asset2.asset_value);
13340
+ const result = await resolveScopeUpdate(asset2, mode, rawHosts);
13341
+ if (!result) return;
13342
+ await client.assets.update(asset2.id, {
13343
+ scope_filter: result.scope_filter,
13344
+ scope_hosts: result.scope_hosts
13345
+ });
13346
+ if (opts.json || opts.output === "json") {
13347
+ printJson(result);
13348
+ return;
13349
+ }
13350
+ printSuccess(`Scope updated: ${result.scope_filter}`);
13351
+ console.log("Hosts:");
13352
+ for (const host of result.scope_hosts) {
13353
+ console.log(` - ${host}`);
13354
+ }
13355
+ } catch (error2) {
13356
+ handleError(error2);
13357
+ }
13358
+ });
12441
13359
  registerAssetAuthCommand(asset);
12442
13360
  }
12443
13361
 
12444
13362
  // src/commands/finding.ts
12445
- var import_picocolors10 = __toESM(require_picocolors(), 1);
13363
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
12446
13364
  function formatPoc(poc) {
12447
13365
  if (poc == null) return null;
12448
13366
  if (typeof poc === "string") return poc.trim() || null;
@@ -12483,7 +13401,7 @@ Examples:
12483
13401
  ])
12484
13402
  );
12485
13403
  if (result.next_cursor) {
12486
- console.log(import_picocolors10.default.dim(`
13404
+ console.log(import_picocolors11.default.dim(`
12487
13405
  More results available. Use --cursor ${result.next_cursor}`));
12488
13406
  }
12489
13407
  } catch (error2) {
@@ -12571,7 +13489,7 @@ Examples:
12571
13489
  }
12572
13490
 
12573
13491
  // src/commands/report.ts
12574
- var import_picocolors11 = __toESM(require_picocolors(), 1);
13492
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
12575
13493
  function renderMarkdown(data) {
12576
13494
  const { scan, findings } = data;
12577
13495
  const lines = [];
@@ -12648,7 +13566,7 @@ Examples:
12648
13566
  ])
12649
13567
  );
12650
13568
  if (result.next_cursor) {
12651
- console.log(import_picocolors11.default.dim(`
13569
+ console.log(import_picocolors12.default.dim(`
12652
13570
  More results available. Use --cursor ${result.next_cursor}`));
12653
13571
  }
12654
13572
  } catch (error2) {
@@ -12719,7 +13637,7 @@ Examples:
12719
13637
  }
12720
13638
 
12721
13639
  // src/commands/token.ts
12722
- var import_picocolors12 = __toESM(require_picocolors(), 1);
13640
+ var import_picocolors13 = __toESM(require_picocolors(), 1);
12723
13641
  function registerTokenCommand(program3) {
12724
13642
  const token = program3.command("token").description("Manage API tokens");
12725
13643
  token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
@@ -12754,7 +13672,7 @@ Examples:
12754
13672
  ])
12755
13673
  );
12756
13674
  if (result.next_cursor) {
12757
- console.log(import_picocolors12.default.dim(`
13675
+ console.log(import_picocolors13.default.dim(`
12758
13676
  More results available. Use --cursor ${result.next_cursor}`));
12759
13677
  }
12760
13678
  } catch (error2) {
@@ -12781,7 +13699,7 @@ Examples:
12781
13699
  }
12782
13700
  printSuccess(`Token created: ${result.name}`);
12783
13701
  console.log("");
12784
- console.log(import_picocolors12.default.bold(" Token: ") + result.token);
13702
+ console.log(import_picocolors13.default.bold(" Token: ") + result.token);
12785
13703
  console.log("");
12786
13704
  printWarning("Save this token \u2014 it will not be shown again.");
12787
13705
  console.log(` ID: ${result.id}`);
@@ -12814,7 +13732,7 @@ Examples:
12814
13732
 
12815
13733
  // src/cli.ts
12816
13734
  var program2 = new Command();
12817
- program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.9.0", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
13735
+ program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.10.1", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
12818
13736
  registerAuthCommand(program2);
12819
13737
  registerMeCommand(program2);
12820
13738
  registerCreditsCommand(program2);
@@ -12823,7 +13741,7 @@ registerAssetCommand(program2);
12823
13741
  registerFindingCommand(program2);
12824
13742
  registerReportCommand(program2);
12825
13743
  registerTokenCommand(program2);
12826
- program2.parseAsync(process.argv).then(() => checkForUpdate("0.9.0")).catch(handleError);
13744
+ program2.parseAsync(process.argv).then(() => checkForUpdate("0.10.1")).catch(handleError);
12827
13745
  /*! Bundled license information:
12828
13746
 
12829
13747
  ky/distribution/index.js: