seo 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3742,10 +3742,10 @@ export declare const crawlPageSnapshotSchema: z.ZodObject<{
3742
3742
  raw: z.ZodString;
3743
3743
  resolved: z.ZodOptional<z.ZodString>;
3744
3744
  ignoredReason: z.ZodOptional<z.ZodEnum<{
3745
+ "invalid-url": "invalid-url";
3745
3746
  "outside-head": "outside-head";
3746
3747
  "alternate-qualifier": "alternate-qualifier";
3747
3748
  fragment: "fragment";
3748
- "invalid-url": "invalid-url";
3749
3749
  "non-http-url": "non-http-url";
3750
3750
  }>>;
3751
3751
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -4379,15 +4379,15 @@ export declare const crawlReportSummarySchema: z.ZodObject<{
4379
4379
  skippedUrls: z.ZodNumber;
4380
4380
  skipReasons: z.ZodArray<z.ZodObject<{
4381
4381
  reason: z.ZodEnum<{
4382
- "invalid-url": "invalid-url";
4383
- "robots-disallowed": "robots-disallowed";
4384
- "origin-backpressure": "origin-backpressure";
4385
4382
  "asset-url": "asset-url";
4386
4383
  "configured-exclusion": "configured-exclusion";
4387
4384
  "duplicate-normalization": "duplicate-normalization";
4385
+ "invalid-url": "invalid-url";
4388
4386
  "memory-pressure": "memory-pressure";
4389
4387
  "off-origin": "off-origin";
4388
+ "origin-backpressure": "origin-backpressure";
4390
4389
  "queue-safety-limit": "queue-safety-limit";
4390
+ "robots-disallowed": "robots-disallowed";
4391
4391
  "robots-uncertain": "robots-uncertain";
4392
4392
  "legacy-unclassified": "legacy-unclassified";
4393
4393
  }>;
@@ -4545,15 +4545,15 @@ export declare const crawlReportSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4545
4545
  skippedUrls: z.ZodNumber;
4546
4546
  skipReasons: z.ZodArray<z.ZodObject<{
4547
4547
  reason: z.ZodEnum<{
4548
- "invalid-url": "invalid-url";
4549
- "robots-disallowed": "robots-disallowed";
4550
- "origin-backpressure": "origin-backpressure";
4551
4548
  "asset-url": "asset-url";
4552
4549
  "configured-exclusion": "configured-exclusion";
4553
4550
  "duplicate-normalization": "duplicate-normalization";
4551
+ "invalid-url": "invalid-url";
4554
4552
  "memory-pressure": "memory-pressure";
4555
4553
  "off-origin": "off-origin";
4554
+ "origin-backpressure": "origin-backpressure";
4556
4555
  "queue-safety-limit": "queue-safety-limit";
4556
+ "robots-disallowed": "robots-disallowed";
4557
4557
  "robots-uncertain": "robots-uncertain";
4558
4558
  "legacy-unclassified": "legacy-unclassified";
4559
4559
  }>;
@@ -4925,10 +4925,10 @@ export declare const crawlReportSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4925
4925
  raw: z.ZodString;
4926
4926
  resolved: z.ZodOptional<z.ZodString>;
4927
4927
  ignoredReason: z.ZodOptional<z.ZodEnum<{
4928
+ "invalid-url": "invalid-url";
4928
4929
  "outside-head": "outside-head";
4929
4930
  "alternate-qualifier": "alternate-qualifier";
4930
4931
  fragment: "fragment";
4931
- "invalid-url": "invalid-url";
4932
4932
  "non-http-url": "non-http-url";
4933
4933
  }>>;
4934
4934
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -5850,15 +5850,15 @@ export declare const crawlReportSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
5850
5850
  skippedUrls: z.ZodNumber;
5851
5851
  skipReasons: z.ZodArray<z.ZodObject<{
5852
5852
  reason: z.ZodEnum<{
5853
- "invalid-url": "invalid-url";
5854
- "robots-disallowed": "robots-disallowed";
5855
- "origin-backpressure": "origin-backpressure";
5856
5853
  "asset-url": "asset-url";
5857
5854
  "configured-exclusion": "configured-exclusion";
5858
5855
  "duplicate-normalization": "duplicate-normalization";
5856
+ "invalid-url": "invalid-url";
5859
5857
  "memory-pressure": "memory-pressure";
5860
5858
  "off-origin": "off-origin";
5859
+ "origin-backpressure": "origin-backpressure";
5861
5860
  "queue-safety-limit": "queue-safety-limit";
5861
+ "robots-disallowed": "robots-disallowed";
5862
5862
  "robots-uncertain": "robots-uncertain";
5863
5863
  "legacy-unclassified": "legacy-unclassified";
5864
5864
  }>;
@@ -6230,10 +6230,10 @@ export declare const crawlReportSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
6230
6230
  raw: z.ZodString;
6231
6231
  resolved: z.ZodOptional<z.ZodString>;
6232
6232
  ignoredReason: z.ZodOptional<z.ZodEnum<{
6233
+ "invalid-url": "invalid-url";
6233
6234
  "outside-head": "outside-head";
6234
6235
  "alternate-qualifier": "alternate-qualifier";
6235
6236
  fragment: "fragment";
6236
- "invalid-url": "invalid-url";
6237
6237
  "non-http-url": "non-http-url";
6238
6238
  }>>;
6239
6239
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -7155,15 +7155,15 @@ export declare const crawlReportSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
7155
7155
  skippedUrls: z.ZodNumber;
7156
7156
  skipReasons: z.ZodArray<z.ZodObject<{
7157
7157
  reason: z.ZodEnum<{
7158
- "invalid-url": "invalid-url";
7159
- "robots-disallowed": "robots-disallowed";
7160
- "origin-backpressure": "origin-backpressure";
7161
7158
  "asset-url": "asset-url";
7162
7159
  "configured-exclusion": "configured-exclusion";
7163
7160
  "duplicate-normalization": "duplicate-normalization";
7161
+ "invalid-url": "invalid-url";
7164
7162
  "memory-pressure": "memory-pressure";
7165
7163
  "off-origin": "off-origin";
7164
+ "origin-backpressure": "origin-backpressure";
7166
7165
  "queue-safety-limit": "queue-safety-limit";
7166
+ "robots-disallowed": "robots-disallowed";
7167
7167
  "robots-uncertain": "robots-uncertain";
7168
7168
  "legacy-unclassified": "legacy-unclassified";
7169
7169
  }>;
@@ -7535,10 +7535,10 @@ export declare const crawlReportSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
7535
7535
  raw: z.ZodString;
7536
7536
  resolved: z.ZodOptional<z.ZodString>;
7537
7537
  ignoredReason: z.ZodOptional<z.ZodEnum<{
7538
+ "invalid-url": "invalid-url";
7538
7539
  "outside-head": "outside-head";
7539
7540
  "alternate-qualifier": "alternate-qualifier";
7540
7541
  fragment: "fragment";
7541
- "invalid-url": "invalid-url";
7542
7542
  "non-http-url": "non-http-url";
7543
7543
  }>>;
7544
7544
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -8462,15 +8462,15 @@ export declare const crawlerSchemas: {
8462
8462
  skippedUrls: z.ZodNumber;
8463
8463
  skipReasons: z.ZodArray<z.ZodObject<{
8464
8464
  reason: z.ZodEnum<{
8465
- "invalid-url": "invalid-url";
8466
- "robots-disallowed": "robots-disallowed";
8467
- "origin-backpressure": "origin-backpressure";
8468
8465
  "asset-url": "asset-url";
8469
8466
  "configured-exclusion": "configured-exclusion";
8470
8467
  "duplicate-normalization": "duplicate-normalization";
8468
+ "invalid-url": "invalid-url";
8471
8469
  "memory-pressure": "memory-pressure";
8472
8470
  "off-origin": "off-origin";
8471
+ "origin-backpressure": "origin-backpressure";
8473
8472
  "queue-safety-limit": "queue-safety-limit";
8473
+ "robots-disallowed": "robots-disallowed";
8474
8474
  "robots-uncertain": "robots-uncertain";
8475
8475
  "legacy-unclassified": "legacy-unclassified";
8476
8476
  }>;
@@ -8842,10 +8842,10 @@ export declare const crawlerSchemas: {
8842
8842
  raw: z.ZodString;
8843
8843
  resolved: z.ZodOptional<z.ZodString>;
8844
8844
  ignoredReason: z.ZodOptional<z.ZodEnum<{
8845
+ "invalid-url": "invalid-url";
8845
8846
  "outside-head": "outside-head";
8846
8847
  "alternate-qualifier": "alternate-qualifier";
8847
8848
  fragment: "fragment";
8848
- "invalid-url": "invalid-url";
8849
8849
  "non-http-url": "non-http-url";
8850
8850
  }>>;
8851
8851
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -9767,15 +9767,15 @@ export declare const crawlerSchemas: {
9767
9767
  skippedUrls: z.ZodNumber;
9768
9768
  skipReasons: z.ZodArray<z.ZodObject<{
9769
9769
  reason: z.ZodEnum<{
9770
- "invalid-url": "invalid-url";
9771
- "robots-disallowed": "robots-disallowed";
9772
- "origin-backpressure": "origin-backpressure";
9773
9770
  "asset-url": "asset-url";
9774
9771
  "configured-exclusion": "configured-exclusion";
9775
9772
  "duplicate-normalization": "duplicate-normalization";
9773
+ "invalid-url": "invalid-url";
9776
9774
  "memory-pressure": "memory-pressure";
9777
9775
  "off-origin": "off-origin";
9776
+ "origin-backpressure": "origin-backpressure";
9778
9777
  "queue-safety-limit": "queue-safety-limit";
9778
+ "robots-disallowed": "robots-disallowed";
9779
9779
  "robots-uncertain": "robots-uncertain";
9780
9780
  "legacy-unclassified": "legacy-unclassified";
9781
9781
  }>;
@@ -10147,10 +10147,10 @@ export declare const crawlerSchemas: {
10147
10147
  raw: z.ZodString;
10148
10148
  resolved: z.ZodOptional<z.ZodString>;
10149
10149
  ignoredReason: z.ZodOptional<z.ZodEnum<{
10150
+ "invalid-url": "invalid-url";
10150
10151
  "outside-head": "outside-head";
10151
10152
  "alternate-qualifier": "alternate-qualifier";
10152
10153
  fragment: "fragment";
10153
- "invalid-url": "invalid-url";
10154
10154
  "non-http-url": "non-http-url";
10155
10155
  }>>;
10156
10156
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -11072,15 +11072,15 @@ export declare const crawlerSchemas: {
11072
11072
  skippedUrls: z.ZodNumber;
11073
11073
  skipReasons: z.ZodArray<z.ZodObject<{
11074
11074
  reason: z.ZodEnum<{
11075
- "invalid-url": "invalid-url";
11076
- "robots-disallowed": "robots-disallowed";
11077
- "origin-backpressure": "origin-backpressure";
11078
11075
  "asset-url": "asset-url";
11079
11076
  "configured-exclusion": "configured-exclusion";
11080
11077
  "duplicate-normalization": "duplicate-normalization";
11078
+ "invalid-url": "invalid-url";
11081
11079
  "memory-pressure": "memory-pressure";
11082
11080
  "off-origin": "off-origin";
11081
+ "origin-backpressure": "origin-backpressure";
11083
11082
  "queue-safety-limit": "queue-safety-limit";
11083
+ "robots-disallowed": "robots-disallowed";
11084
11084
  "robots-uncertain": "robots-uncertain";
11085
11085
  "legacy-unclassified": "legacy-unclassified";
11086
11086
  }>;
@@ -11452,10 +11452,10 @@ export declare const crawlerSchemas: {
11452
11452
  raw: z.ZodString;
11453
11453
  resolved: z.ZodOptional<z.ZodString>;
11454
11454
  ignoredReason: z.ZodOptional<z.ZodEnum<{
11455
+ "invalid-url": "invalid-url";
11455
11456
  "outside-head": "outside-head";
11456
11457
  "alternate-qualifier": "alternate-qualifier";
11457
11458
  fragment: "fragment";
11458
- "invalid-url": "invalid-url";
11459
11459
  "non-http-url": "non-http-url";
11460
11460
  }>>;
11461
11461
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -12881,10 +12881,10 @@ export declare const crawlerSchemas: {
12881
12881
  raw: z.ZodString;
12882
12882
  resolved: z.ZodOptional<z.ZodString>;
12883
12883
  ignoredReason: z.ZodOptional<z.ZodEnum<{
12884
+ "invalid-url": "invalid-url";
12884
12885
  "outside-head": "outside-head";
12885
12886
  "alternate-qualifier": "alternate-qualifier";
12886
12887
  fragment: "fragment";
12887
- "invalid-url": "invalid-url";
12888
12888
  "non-http-url": "non-http-url";
12889
12889
  }>>;
12890
12890
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -13305,15 +13305,15 @@ export declare const crawlerJsonSchemas: {
13305
13305
  skippedUrls: z.ZodNumber;
13306
13306
  skipReasons: z.ZodArray<z.ZodObject<{
13307
13307
  reason: z.ZodEnum<{
13308
- "invalid-url": "invalid-url";
13309
- "robots-disallowed": "robots-disallowed";
13310
- "origin-backpressure": "origin-backpressure";
13311
13308
  "asset-url": "asset-url";
13312
13309
  "configured-exclusion": "configured-exclusion";
13313
13310
  "duplicate-normalization": "duplicate-normalization";
13311
+ "invalid-url": "invalid-url";
13314
13312
  "memory-pressure": "memory-pressure";
13315
13313
  "off-origin": "off-origin";
13314
+ "origin-backpressure": "origin-backpressure";
13316
13315
  "queue-safety-limit": "queue-safety-limit";
13316
+ "robots-disallowed": "robots-disallowed";
13317
13317
  "robots-uncertain": "robots-uncertain";
13318
13318
  "legacy-unclassified": "legacy-unclassified";
13319
13319
  }>;
@@ -13685,10 +13685,10 @@ export declare const crawlerJsonSchemas: {
13685
13685
  raw: z.ZodString;
13686
13686
  resolved: z.ZodOptional<z.ZodString>;
13687
13687
  ignoredReason: z.ZodOptional<z.ZodEnum<{
13688
+ "invalid-url": "invalid-url";
13688
13689
  "outside-head": "outside-head";
13689
13690
  "alternate-qualifier": "alternate-qualifier";
13690
13691
  fragment: "fragment";
13691
- "invalid-url": "invalid-url";
13692
13692
  "non-http-url": "non-http-url";
13693
13693
  }>>;
13694
13694
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -14610,15 +14610,15 @@ export declare const crawlerJsonSchemas: {
14610
14610
  skippedUrls: z.ZodNumber;
14611
14611
  skipReasons: z.ZodArray<z.ZodObject<{
14612
14612
  reason: z.ZodEnum<{
14613
- "invalid-url": "invalid-url";
14614
- "robots-disallowed": "robots-disallowed";
14615
- "origin-backpressure": "origin-backpressure";
14616
14613
  "asset-url": "asset-url";
14617
14614
  "configured-exclusion": "configured-exclusion";
14618
14615
  "duplicate-normalization": "duplicate-normalization";
14616
+ "invalid-url": "invalid-url";
14619
14617
  "memory-pressure": "memory-pressure";
14620
14618
  "off-origin": "off-origin";
14619
+ "origin-backpressure": "origin-backpressure";
14621
14620
  "queue-safety-limit": "queue-safety-limit";
14621
+ "robots-disallowed": "robots-disallowed";
14622
14622
  "robots-uncertain": "robots-uncertain";
14623
14623
  "legacy-unclassified": "legacy-unclassified";
14624
14624
  }>;
@@ -14990,10 +14990,10 @@ export declare const crawlerJsonSchemas: {
14990
14990
  raw: z.ZodString;
14991
14991
  resolved: z.ZodOptional<z.ZodString>;
14992
14992
  ignoredReason: z.ZodOptional<z.ZodEnum<{
14993
+ "invalid-url": "invalid-url";
14993
14994
  "outside-head": "outside-head";
14994
14995
  "alternate-qualifier": "alternate-qualifier";
14995
14996
  fragment: "fragment";
14996
- "invalid-url": "invalid-url";
14997
14997
  "non-http-url": "non-http-url";
14998
14998
  }>>;
14999
14999
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -15915,15 +15915,15 @@ export declare const crawlerJsonSchemas: {
15915
15915
  skippedUrls: z.ZodNumber;
15916
15916
  skipReasons: z.ZodArray<z.ZodObject<{
15917
15917
  reason: z.ZodEnum<{
15918
- "invalid-url": "invalid-url";
15919
- "robots-disallowed": "robots-disallowed";
15920
- "origin-backpressure": "origin-backpressure";
15921
15918
  "asset-url": "asset-url";
15922
15919
  "configured-exclusion": "configured-exclusion";
15923
15920
  "duplicate-normalization": "duplicate-normalization";
15921
+ "invalid-url": "invalid-url";
15924
15922
  "memory-pressure": "memory-pressure";
15925
15923
  "off-origin": "off-origin";
15924
+ "origin-backpressure": "origin-backpressure";
15926
15925
  "queue-safety-limit": "queue-safety-limit";
15926
+ "robots-disallowed": "robots-disallowed";
15927
15927
  "robots-uncertain": "robots-uncertain";
15928
15928
  "legacy-unclassified": "legacy-unclassified";
15929
15929
  }>;
@@ -16295,10 +16295,10 @@ export declare const crawlerJsonSchemas: {
16295
16295
  raw: z.ZodString;
16296
16296
  resolved: z.ZodOptional<z.ZodString>;
16297
16297
  ignoredReason: z.ZodOptional<z.ZodEnum<{
16298
+ "invalid-url": "invalid-url";
16298
16299
  "outside-head": "outside-head";
16299
16300
  "alternate-qualifier": "alternate-qualifier";
16300
16301
  fragment: "fragment";
16301
- "invalid-url": "invalid-url";
16302
16302
  "non-http-url": "non-http-url";
16303
16303
  }>>;
16304
16304
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -17724,10 +17724,10 @@ export declare const crawlerJsonSchemas: {
17724
17724
  raw: z.ZodString;
17725
17725
  resolved: z.ZodOptional<z.ZodString>;
17726
17726
  ignoredReason: z.ZodOptional<z.ZodEnum<{
17727
+ "invalid-url": "invalid-url";
17727
17728
  "outside-head": "outside-head";
17728
17729
  "alternate-qualifier": "alternate-qualifier";
17729
17730
  fragment: "fragment";
17730
- "invalid-url": "invalid-url";
17731
17731
  "non-http-url": "non-http-url";
17732
17732
  }>>;
17733
17733
  streamedMetadata: z.ZodOptional<z.ZodLiteral<true>>;
@@ -18046,12 +18046,12 @@ export declare const providerPackageNameSchema: z.ZodString;
18046
18046
  export declare const providerExactVersionSchema: z.ZodString;
18047
18047
  export declare const providerIntegritySchema: z.ZodString;
18048
18048
  export declare const providerKindSchema: z.ZodEnum<{
18049
- "local-search": "local-search";
18050
18049
  "traffic-analytics": "traffic-analytics";
18051
18050
  "search-results": "search-results";
18052
18051
  "keyword-data": "keyword-data";
18053
18052
  "domain-data": "domain-data";
18054
18053
  "link-data": "link-data";
18054
+ "local-search": "local-search";
18055
18055
  "ai-data": "ai-data";
18056
18056
  other: "other";
18057
18057
  }>;
@@ -18094,12 +18094,12 @@ export declare const providerRegistrationSchema: z.ZodObject<{
18094
18094
  displayName: z.ZodString;
18095
18095
  description: z.ZodString;
18096
18096
  kinds: z.ZodArray<z.ZodEnum<{
18097
- "local-search": "local-search";
18098
18097
  "traffic-analytics": "traffic-analytics";
18099
18098
  "search-results": "search-results";
18100
18099
  "keyword-data": "keyword-data";
18101
18100
  "domain-data": "domain-data";
18102
18101
  "link-data": "link-data";
18102
+ "local-search": "local-search";
18103
18103
  "ai-data": "ai-data";
18104
18104
  other: "other";
18105
18105
  }>>;
@@ -18172,6 +18172,7 @@ export declare const providerIdSchema: z.ZodString;
18172
18172
  export type ProviderId = z.infer<typeof providerIdSchema>;
18173
18173
  export type ExternalServiceId = string;
18174
18174
  export declare const providerCapabilitySchema: z.ZodEnum<{
18175
+ "local-search": "local-search";
18175
18176
  "serp-snapshot": "serp-snapshot";
18176
18177
  "keyword-metrics": "keyword-metrics";
18177
18178
  "keyword-discovery": "keyword-discovery";
@@ -18183,7 +18184,6 @@ export declare const providerCapabilitySchema: z.ZodEnum<{
18183
18184
  "referring-domains": "referring-domains";
18184
18185
  backlinks: "backlinks";
18185
18186
  "domain-rating": "domain-rating";
18186
- "local-search": "local-search";
18187
18187
  "ai-mentions": "ai-mentions";
18188
18188
  "ai-prompt-observation": "ai-prompt-observation";
18189
18189
  }>;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
2
- import { n as version, t as load } from "./esm-BIXGSxFJ.js";
2
+ import { n as version, t as load } from "./esm-Dmtr8qrP.js";
3
3
  import { SEO_PROVIDER_API_VERSION } from "./provider-sdk.js";
4
4
  import crypto, { createHash, randomUUID, sign } from "node:crypto";
5
5
  import { z } from "zod";
@@ -29357,25 +29357,64 @@ function need(headers, provider) {
29357
29357
  if (values.some((value) => !value)) throw new CommandError("NO_KEY", `No ${provider} key. Hosted MCP: save it at https://seoskill.dev/account#keys. CLI and local MCP: run \`seo login\` to use the account's keys, or \`seo providers ${provider} connect\`.`);
29358
29358
  return values;
29359
29359
  }
29360
+ const TASK_TIMEOUT_MS = 6e4;
29361
+ const READ_TIMEOUT_MS = 3e4;
29362
+ const RETRY_DELAY_MS = 250;
29363
+ const retryStatus = (status) => status === 429 || status >= 500;
29360
29364
  async function call(provider, request) {
29361
- const response = await fetch(request);
29362
- if (response.ok) return response;
29363
- const text = (await response.text()).slice(0, 300);
29364
- throw new CommandError(response.status === 401 || response.status === 403 ? "NO_KEY" : "PROVIDER_ERROR", `${provider} answered ${response.status}. ${text}`);
29365
+ const read = request.method === "GET";
29366
+ const timeoutMs = read ? READ_TIMEOUT_MS : TASK_TIMEOUT_MS;
29367
+ for (let attempt = 1;; attempt += 1) {
29368
+ const last = attempt === 2;
29369
+ let response;
29370
+ try {
29371
+ response = await fetch(request.clone(), { signal: AbortSignal.timeout(timeoutMs) });
29372
+ } catch (error) {
29373
+ if (read && !last) {
29374
+ await wait(RETRY_DELAY_MS);
29375
+ continue;
29376
+ }
29377
+ throw new CommandError("PROVIDER_ERROR", error.name === "TimeoutError" ? `${provider} did not answer within ${timeoutMs / 1e3} seconds.` : `${provider} could not be reached: ${error.message}`);
29378
+ }
29379
+ if (response.ok) return response;
29380
+ if (retryStatus(response.status) && !last) {
29381
+ await response.body?.cancel();
29382
+ await wait(RETRY_DELAY_MS);
29383
+ continue;
29384
+ }
29385
+ const text = (await response.text()).slice(0, 300);
29386
+ throw new CommandError(response.status === 401 || response.status === 403 ? "NO_KEY" : response.status === 429 ? "RATE_LIMITED" : "PROVIDER_ERROR", `${provider} answered ${response.status}. ${text}`);
29387
+ }
29365
29388
  }
29389
+ const wait = (ms) => new Promise((done) => setTimeout(done, ms));
29366
29390
  function requireInput(input) {
29367
29391
  const needsKeywords = input.action === "keyword_metrics" || input.action === "serp";
29368
29392
  if (needsKeywords && input.keywords.length === 0) throw new CommandError("INVALID_INPUT", `${input.action} needs keywords.`);
29369
29393
  if (!needsKeywords && !input.target) throw new CommandError("INVALID_INPUT", `${input.action} needs a target domain or URL.`);
29370
29394
  }
29395
+ async function eachKeyword(keywords, missed, rowsFor) {
29396
+ const rows = [];
29397
+ let failure;
29398
+ for (const keyword of keywords) try {
29399
+ rows.push(...await rowsFor(keyword));
29400
+ } catch (error) {
29401
+ if (error instanceof CommandError && error.code === "NO_KEY") throw error;
29402
+ failure ??= error;
29403
+ missed.push(keyword);
29404
+ }
29405
+ if (missed.length === keywords.length) throw failure;
29406
+ return rows;
29407
+ }
29371
29408
  async function research(provider, headers, input) {
29372
29409
  requireInput(input);
29373
29410
  const keys = need(headers, provider);
29374
29411
  if (provider === "dataforseo") return dataForSeo(keys, input);
29412
+ const missed = [];
29375
29413
  return {
29376
- rows: await ahrefs(keys[0] ?? "", input),
29414
+ rows: await ahrefs(keys[0] ?? "", input, missed),
29377
29415
  costUsd: null,
29378
- partial: false
29416
+ partial: false,
29417
+ missed
29379
29418
  };
29380
29419
  }
29381
29420
  function countryName(code) {
@@ -29441,12 +29480,14 @@ const PARTIAL = 40106;
29441
29480
  async function dataForSeo(keys, input) {
29442
29481
  const spent = {
29443
29482
  cost: 0,
29444
- partial: false
29483
+ partial: false,
29484
+ missed: []
29445
29485
  };
29446
29486
  return {
29447
29487
  rows: await dataForSeoRows(keys, input, spent),
29448
29488
  costUsd: Math.round(spent.cost * 1e4) / 1e4,
29449
- partial: spent.partial
29489
+ partial: spent.partial,
29490
+ missed: spent.missed
29450
29491
  };
29451
29492
  }
29452
29493
  async function dataForSeoRows(keys, input, spent) {
@@ -29488,28 +29529,20 @@ async function dataForSeoRows(keys, input, spent) {
29488
29529
  keyword_difficulty: "keyword_data.keyword_properties.keyword_difficulty",
29489
29530
  main_intent: "keyword_data.search_intent_info.main_intent"
29490
29531
  }));
29491
- if (input.action === "serp") {
29492
- const rows = [];
29493
- for (const keyword of input.keywords.slice(0, 5)) {
29494
- const items = await post("v3/serp/google/organic/live/advanced", {
29495
- keyword,
29496
- depth: Math.min(input.limit, 100)
29497
- });
29498
- for (const item of items) {
29499
- if (item.type !== "organic") continue;
29500
- rows.push({
29501
- keyword,
29502
- ...pick(item, {
29503
- position: "rank_absolute",
29504
- url: "url",
29505
- domain: "domain",
29506
- title: "title"
29507
- })
29508
- });
29509
- }
29510
- }
29511
- return rows;
29512
- }
29532
+ if (input.action === "serp") return eachKeyword(input.keywords.slice(0, 5), spent.missed, async (keyword) => {
29533
+ return (await post("v3/serp/google/organic/live/advanced", {
29534
+ keyword,
29535
+ depth: Math.min(input.limit, 100)
29536
+ })).filter((item) => item.type === "organic").map((item) => ({
29537
+ keyword,
29538
+ ...pick(item, {
29539
+ position: "rank_absolute",
29540
+ url: "url",
29541
+ domain: "domain",
29542
+ title: "title"
29543
+ })
29544
+ }));
29545
+ });
29513
29546
  if (input.action === "backlinks") return (await post("v3/backlinks/referring_domains/live", {
29514
29547
  target: bareTarget(input.target),
29515
29548
  limit: input.limit,
@@ -29538,7 +29571,7 @@ function isPage(target) {
29538
29571
  function bareTarget(target) {
29539
29572
  return target.replace(/^https?:\/\//, "").replace(/\/$/, "");
29540
29573
  }
29541
- async function ahrefs(key, input) {
29574
+ async function ahrefs(key, input, missed) {
29542
29575
  const get = async (path, params) => {
29543
29576
  const url = new URL(`https://api.ahrefs.com/v3/${path}`);
29544
29577
  for (const [name, value] of Object.entries(params)) url.searchParams.set(name, value);
@@ -29562,22 +29595,17 @@ async function ahrefs(key, input) {
29562
29595
  limit: String(input.limit),
29563
29596
  order_by: "sum_traffic:desc"
29564
29597
  })).keywords ?? [];
29565
- if (input.action === "serp") {
29566
- const rows = [];
29567
- for (const keyword of input.keywords.slice(0, 5)) {
29568
- const data = await get("serp-overview/serp-overview", {
29569
- select: "position,type,url,title,domain_rating,traffic,keywords",
29570
- keyword,
29571
- country: input.country,
29572
- top_positions: String(Math.min(input.limit, 100))
29573
- });
29574
- for (const row of data.positions ?? []) rows.push({
29575
- keyword,
29576
- ...row
29577
- });
29578
- }
29579
- return rows;
29580
- }
29598
+ if (input.action === "serp") return eachKeyword(input.keywords.slice(0, 5), missed, async (keyword) => {
29599
+ return ((await get("serp-overview/serp-overview", {
29600
+ select: "position,type,url,title,domain_rating,traffic,keywords",
29601
+ keyword,
29602
+ country: input.country,
29603
+ top_positions: String(Math.min(input.limit, 100))
29604
+ })).positions ?? []).map((row) => ({
29605
+ keyword,
29606
+ ...row
29607
+ }));
29608
+ });
29581
29609
  if (input.action === "backlinks") return (await get("site-explorer/refdomains", {
29582
29610
  select: "domain,domain_rating,links_to_target,dofollow_links,first_seen,last_seen",
29583
29611
  target: input.target,
@@ -32245,7 +32273,7 @@ async function runResearch(input, keys, options) {
32245
32273
  const offset = input.offset ?? 0;
32246
32274
  const language = input.provider === "dataforseo" ? dataForSeoLanguage(country) : null;
32247
32275
  if (input.action === "ai_answers") throw new CommandError("INVALID_INPUT", "ai_answers runs on its own.");
32248
- const { rows: found, costUsd, partial } = await research(input.provider, keys, {
32276
+ const { rows: found, costUsd, partial, missed } = await research(input.provider, keys, {
32249
32277
  action: input.action,
32250
32278
  keywords: input.keywords ?? [],
32251
32279
  target,
@@ -32271,6 +32299,7 @@ async function runResearch(input, keys, options) {
32271
32299
  `${input.provider} estimates, in the provider’s own units.`,
32272
32300
  ...costUsd !== null ? [`DataForSEO cost: $${costUsd}.`] : [],
32273
32301
  ...partial ? ["Partial: DataForSEO could not fetch some pages of results, so rows may be missing."] : [],
32302
+ ...missed.length ? [`Partial: ${input.provider} failed for ${missed.join(", ")}, so those keywords have no rows. Ask for them again.`] : [],
32274
32303
  ...note ? [note] : [],
32275
32304
  ...hasTargetDr ? [`${bareDomain(target)} domain_rating: ${targetDr}.`] : [],
32276
32305
  ...rated.added || hasTargetDr ? [DR_ATTRIBUTION] : []
@@ -32288,6 +32317,7 @@ async function runResearch(input, keys, options) {
32288
32317
  leftOut: note,
32289
32318
  costUsd,
32290
32319
  partial,
32320
+ missedKeywords: missed,
32291
32321
  total: rated.rows.length,
32292
32322
  offset,
32293
32323
  rows: rated.rows.slice(offset, offset + 50)