@siteoshq/cli 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8710,16 +8710,8 @@ async function runAnalyticsCommand(options) {
8710
8710
  }
8711
8711
  }
8712
8712
 
8713
- // src/services/seo-command.ts
8714
- import { writeFile as writeFile10 } from "fs/promises";
8715
- import path28 from "path";
8716
- import { parseArgs as parseArgs8 } from "util";
8717
- import { z as z19 } from "zod";
8718
-
8719
- // src/services/seo-research-command.ts
8720
- import { randomUUID as randomUUID6 } from "crypto";
8721
- import { setTimeout as setTimeout2 } from "timers/promises";
8722
- import { isDeepStrictEqual, parseArgs as parseArgs5 } from "util";
8713
+ // src/services/seo-repair-command.ts
8714
+ import { parseArgs as parseArgs5 } from "util";
8723
8715
  import { z as z16 } from "zod";
8724
8716
 
8725
8717
  // src/services/seo-report-client.ts
@@ -8823,7 +8815,110 @@ function seoReportFailure(cause, json, idempotencyKey) {
8823
8815
  };
8824
8816
  }
8825
8817
 
8818
+ // src/services/seo-repair-command.ts
8819
+ async function runSeoRepairCommand(options) {
8820
+ const json = options.args.includes("--json");
8821
+ try {
8822
+ const { positionals, values } = parseArgs5({
8823
+ args: options.args,
8824
+ allowPositionals: true,
8825
+ strict: true,
8826
+ options: {
8827
+ json: { type: "boolean" },
8828
+ environment: { type: "string" },
8829
+ resource: { type: "string" },
8830
+ audit: { type: "string" },
8831
+ url: { type: "string", multiple: true },
8832
+ rule: { type: "string" }
8833
+ }
8834
+ });
8835
+ const recheck = positionals[0] === "recheck";
8836
+ if (positionals.length !== 1 || !recheck && positionals[0] !== "repair" || recheck && values.rule)
8837
+ throw new Error(
8838
+ "Use siteos seo repair or siteos seo recheck with documented flags."
8839
+ );
8840
+ if (!values.audit || values.audit.length > 160 || !values.url?.length || values.url.length > 20)
8841
+ throw new Error(
8842
+ "Choose a source --audit and between 1 and 20 --url values."
8843
+ );
8844
+ if (values.rule && values.rule.length > 120)
8845
+ throw new Error("Use a documented rule ID.");
8846
+ for (const value of values.url) {
8847
+ const url = new URL(value);
8848
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.port || value.length > 2048)
8849
+ throw new Error("Use eligible public HTTP(S) URLs.");
8850
+ }
8851
+ const client = await seoReportClient(options, values.environment);
8852
+ if (values.resource && client.resourceId !== values.resource)
8853
+ throw new Error(
8854
+ "The selected Project environment does not match --resource. Select the environment from the repair brief first."
8855
+ );
8856
+ const urls = [...new Set(values.url)];
8857
+ const response = recheck ? await client.request("/rechecks", "seo:audits:write", {
8858
+ auditId: values.audit,
8859
+ urls
8860
+ }) : await client.request("/repair-brief", "seo:workspace:read", {
8861
+ audit: values.audit,
8862
+ urls,
8863
+ ...values.rule ? { rule: values.rule } : {}
8864
+ });
8865
+ const record = versionedReport.parse(await response.json());
8866
+ if (recheck) {
8867
+ z16.object({
8868
+ id: z16.string(),
8869
+ resourceId: z16.literal(client.resourceId),
8870
+ organizationId: z16.literal(client.organizationId),
8871
+ state: z16.literal("queued")
8872
+ }).parse(record.audit);
8873
+ return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
8874
+ }
8875
+ const brief = z16.object({
8876
+ schemaVersion: z16.literal(1),
8877
+ context: z16.object({
8878
+ auditId: z16.literal(values.audit),
8879
+ resourceId: z16.literal(client.resourceId),
8880
+ organizationId: z16.literal(client.organizationId),
8881
+ selectedUrls: z16.array(z16.string()).min(1).max(20),
8882
+ ruleId: z16.literal(values.rule ?? null)
8883
+ }),
8884
+ evidence: z16.array(
8885
+ z16.object({
8886
+ url: z16.string(),
8887
+ findings: z16.array(z16.unknown()).max(12)
8888
+ })
8889
+ ).min(1).max(20),
8890
+ suggestedGroups: z16.array(z16.unknown()),
8891
+ verificationCommand: z16.string().min(1),
8892
+ text: z16.string().min(1)
8893
+ }).parse(record);
8894
+ const canonical = (value) => {
8895
+ const url = new URL(value);
8896
+ url.hash = "";
8897
+ return url.href;
8898
+ };
8899
+ const expected = new Set(urls.map(canonical));
8900
+ if (brief.context.selectedUrls.length !== expected.size || new Set(brief.context.selectedUrls).size !== expected.size || brief.context.selectedUrls.some((url) => !expected.has(url)) || brief.evidence.length !== expected.size || new Set(brief.evidence.map((page) => page.url)).size !== expected.size || brief.evidence.some((page) => !expected.has(page.url)))
8901
+ throw new Error("The repair brief does not match the selected URLs.");
8902
+ return {
8903
+ exitCode: 0,
8904
+ stdout: json ? JSON.stringify(record, null, 2) : brief.text
8905
+ };
8906
+ } catch (cause) {
8907
+ return seoReportFailure(cause, json);
8908
+ }
8909
+ }
8910
+
8911
+ // src/services/seo-command.ts
8912
+ import { writeFile as writeFile10 } from "fs/promises";
8913
+ import path28 from "path";
8914
+ import { parseArgs as parseArgs9 } from "util";
8915
+ import { z as z20 } from "zod";
8916
+
8826
8917
  // src/services/seo-research-command.ts
8918
+ import { randomUUID as randomUUID6 } from "crypto";
8919
+ import { setTimeout as setTimeout2 } from "timers/promises";
8920
+ import { isDeepStrictEqual, parseArgs as parseArgs6 } from "util";
8921
+ import { z as z17 } from "zod";
8827
8922
  var RESEARCH_HELP = `
8828
8923
  siteos seo research summary [--environment <slug>] [--json]
8829
8924
  siteos seo research status --kind <kind> [--environment <slug>] [--json]
@@ -8864,11 +8959,11 @@ var kinds = [
8864
8959
  "brand",
8865
8960
  "ai-visibility"
8866
8961
  ];
8867
- var requestSchema = z16.object({
8868
- kind: z16.enum(["domain", ...kinds]).transform((kind) => kind === "domain" ? "competitors" : kind),
8869
- target: z16.string().min(1).max(253)
8962
+ var requestSchema = z17.object({
8963
+ kind: z17.enum(["domain", ...kinds]).transform((kind) => kind === "domain" ? "competitors" : kind),
8964
+ target: z17.string().min(1).max(253)
8870
8965
  }).passthrough();
8871
- var runStates = z16.enum([
8966
+ var runStates = z17.enum([
8872
8967
  "queued",
8873
8968
  "running",
8874
8969
  "completed",
@@ -8884,7 +8979,7 @@ var csvCell = (value) => {
8884
8979
  async function runSeoResearchCommand(options) {
8885
8980
  let idempotencyKey;
8886
8981
  try {
8887
- const { values, positionals } = parseArgs5({
8982
+ const { values, positionals } = parseArgs6({
8888
8983
  args: options.args.slice(1),
8889
8984
  strict: true,
8890
8985
  allowPositionals: true,
@@ -8965,26 +9060,26 @@ async function runSeoResearchCommand(options) {
8965
9060
  );
8966
9061
  }
8967
9062
  const client = await seoReportClient(options, values.environment);
8968
- const resourceSchema = z16.object({
8969
- id: z16.literal(client.resourceId),
8970
- organizationId: z16.literal(client.organizationId)
9063
+ const resourceSchema = z17.object({
9064
+ id: z17.literal(client.resourceId),
9065
+ organizationId: z17.literal(client.organizationId)
8971
9066
  }).passthrough();
8972
- const runSchema = z16.object({
8973
- id: z16.string(),
8974
- resourceId: z16.literal(client.resourceId),
8975
- organizationId: z16.literal(client.organizationId),
8976
- websiteUrl: z16.string(),
9067
+ const runSchema = z17.object({
9068
+ id: z17.string(),
9069
+ resourceId: z17.literal(client.resourceId),
9070
+ organizationId: z17.literal(client.organizationId),
9071
+ websiteUrl: z17.string(),
8977
9072
  request: requestSchema,
8978
9073
  state: runStates,
8979
- createdAt: z16.string(),
8980
- finishedAt: z16.string().nullable(),
8981
- parts: z16.array(
8982
- z16.object({
8983
- key: z16.string(),
8984
- state: z16.enum(["completed", "failed"]),
8985
- error: z16.string().nullable(),
8986
- dataset: z16.record(z16.unknown()).nullable(),
8987
- observedAt: z16.string()
9074
+ createdAt: z17.string(),
9075
+ finishedAt: z17.string().nullable(),
9076
+ parts: z17.array(
9077
+ z17.object({
9078
+ key: z17.string(),
9079
+ state: z17.enum(["completed", "failed"]),
9080
+ error: z17.string().nullable(),
9081
+ dataset: z17.record(z17.unknown()).nullable(),
9082
+ observedAt: z17.string()
8988
9083
  }).passthrough()
8989
9084
  )
8990
9085
  }).passthrough();
@@ -8998,36 +9093,36 @@ async function runSeoResearchCommand(options) {
8998
9093
  const query = `?kind=${encodeURIComponent(values.kind ?? "rankings")}`;
8999
9094
  let record;
9000
9095
  if (action.startsWith("serp ")) {
9001
- const snapshotSchema = z16.object({
9002
- id: z16.string(),
9003
- organizationId: z16.literal(client.organizationId),
9004
- resourceId: z16.literal(client.resourceId),
9005
- sourceRunId: z16.literal(id),
9096
+ const snapshotSchema = z17.object({
9097
+ id: z17.string(),
9098
+ organizationId: z17.literal(client.organizationId),
9099
+ resourceId: z17.literal(client.resourceId),
9100
+ sourceRunId: z17.literal(id),
9006
9101
  request: requestSchema,
9007
- keywordKey: z16.literal(
9102
+ keywordKey: z17.literal(
9008
9103
  values.keyword.normalize("NFKC").trim().toLowerCase().replace(/\s+/gu, " ")
9009
9104
  ),
9010
- state: z16.enum([
9105
+ state: z17.enum([
9011
9106
  "queued",
9012
9107
  "running",
9013
9108
  "completed",
9014
9109
  "failed",
9015
9110
  "cancelled"
9016
9111
  ]),
9017
- dataset: z16.object({
9018
- type: z16.literal("serp"),
9019
- keyword: z16.string(),
9020
- country: z16.string(),
9021
- language: z16.string(),
9022
- device: z16.enum(["desktop", "mobile"]),
9023
- observedAt: z16.string(),
9024
- rows: z16.array(
9025
- z16.object({
9026
- position: z16.number(),
9027
- title: z16.string(),
9028
- url: z16.string(),
9029
- domain: z16.string(),
9030
- description: z16.string()
9112
+ dataset: z17.object({
9113
+ type: z17.literal("serp"),
9114
+ keyword: z17.string(),
9115
+ country: z17.string(),
9116
+ language: z17.string(),
9117
+ device: z17.enum(["desktop", "mobile"]),
9118
+ observedAt: z17.string(),
9119
+ rows: z17.array(
9120
+ z17.object({
9121
+ position: z17.number(),
9122
+ title: z17.string(),
9123
+ url: z17.string(),
9124
+ domain: z17.string(),
9125
+ description: z17.string()
9031
9126
  })
9032
9127
  )
9033
9128
  }).nullable()
@@ -9120,9 +9215,9 @@ async function runSeoResearchCommand(options) {
9120
9215
  name: values.name.trim(),
9121
9216
  ...values.id ? { id: values.id } : {}
9122
9217
  });
9123
- const saved = z16.object({
9124
- id: z16.string(),
9125
- name: z16.literal(values.name.trim()),
9218
+ const saved = z17.object({
9219
+ id: z17.string(),
9220
+ name: z17.literal(values.name.trim()),
9126
9221
  request: requestSchema
9127
9222
  }).parse(record.saved);
9128
9223
  if (values.id && saved.id !== values.id || !isDeepStrictEqual(saved.request, plan.request))
@@ -9136,23 +9231,23 @@ async function runSeoResearchCommand(options) {
9136
9231
  action === "cancel" ? "run" : "write",
9137
9232
  {}
9138
9233
  );
9139
- z16.literal(true).parse(
9234
+ z17.literal(true).parse(
9140
9235
  record[action === "cancel" ? "cancelled" : "removed"]
9141
9236
  );
9142
9237
  } else if (action === "summary") {
9143
9238
  record = await get("/summary");
9144
9239
  resourceSchema.parse(record.resource);
9145
- z16.array(z16.object({ kind: z16.enum(kinds), summary: z16.unknown() })).parse(
9240
+ z17.array(z17.object({ kind: z17.enum(kinds), summary: z17.unknown() })).parse(
9146
9241
  record.checks
9147
9242
  );
9148
9243
  } else if (["status", "history", "saved list"].includes(action)) {
9149
9244
  const view = await get(query);
9150
9245
  resourceSchema.parse(view.resource);
9151
- const runs = z16.array(runSchema).parse(view.runs);
9152
- const saved = z16.array(
9153
- z16.object({
9154
- id: z16.string(),
9155
- name: z16.string(),
9246
+ const runs = z17.array(runSchema).parse(view.runs);
9247
+ const saved = z17.array(
9248
+ z17.object({
9249
+ id: z17.string(),
9250
+ name: z17.string(),
9156
9251
  request: requestSchema
9157
9252
  }).passthrough()
9158
9253
  ).parse(view.saved);
@@ -9268,8 +9363,8 @@ async function runSeoResearchCommand(options) {
9268
9363
  }
9269
9364
 
9270
9365
  // src/services/seo-gsc-command.ts
9271
- import { parseArgs as parseArgs6 } from "util";
9272
- import { z as z17 } from "zod";
9366
+ import { parseArgs as parseArgs7 } from "util";
9367
+ import { z as z18 } from "zod";
9273
9368
  var GSC_HELP = `
9274
9369
  siteos seo gsc status [--environment <slug>] [--json]
9275
9370
  siteos seo gsc report [--dataset <pages|queries>] [--query <text>] [--filter <all|issues|declining>] [--sort <clicks|change|impressions>] [--page <number>] [--url <page-url>] [--environment <slug>] [--json]
@@ -9287,7 +9382,7 @@ Bind/disconnect require a current revision and separate settings authority. Disc
9287
9382
  `;
9288
9383
  async function runSeoGscCommand(options) {
9289
9384
  try {
9290
- const { values, positionals } = parseArgs6({
9385
+ const { values, positionals } = parseArgs7({
9291
9386
  args: options.args.slice(1),
9292
9387
  strict: true,
9293
9388
  allowPositionals: true,
@@ -9369,13 +9464,13 @@ async function runSeoGscCommand(options) {
9369
9464
  `seo:search:${action === "sync" ? "sync" : writing ? "write" : "read"}`,
9370
9465
  body
9371
9466
  );
9372
- const runSchema = z17.object({
9373
- id: z17.string(),
9374
- resourceId: z17.literal(client.resourceId),
9375
- organizationId: z17.literal(client.organizationId),
9376
- websiteUrl: z17.string(),
9377
- siteUrl: z17.string(),
9378
- state: z17.enum([
9467
+ const runSchema = z18.object({
9468
+ id: z18.string(),
9469
+ resourceId: z18.literal(client.resourceId),
9470
+ organizationId: z18.literal(client.organizationId),
9471
+ websiteUrl: z18.string(),
9472
+ siteUrl: z18.string(),
9473
+ state: z18.enum([
9379
9474
  "queued",
9380
9475
  "running",
9381
9476
  "completed",
@@ -9383,32 +9478,32 @@ async function runSeoGscCommand(options) {
9383
9478
  "failed",
9384
9479
  "cancelled"
9385
9480
  ]),
9386
- dates: z17.unknown()
9481
+ dates: z18.unknown()
9387
9482
  }).passthrough();
9388
- const bindingSchema = z17.object({
9389
- resourceId: z17.literal(client.resourceId),
9390
- organizationId: z17.literal(client.organizationId),
9391
- websiteUrl: z17.string(),
9392
- siteUrl: z17.string(),
9393
- revision: z17.number().int().positive()
9483
+ const bindingSchema = z18.object({
9484
+ resourceId: z18.literal(client.resourceId),
9485
+ organizationId: z18.literal(client.organizationId),
9486
+ websiteUrl: z18.string(),
9487
+ siteUrl: z18.string(),
9488
+ revision: z18.number().int().positive()
9394
9489
  }).passthrough();
9395
- const metrics = z17.object({
9396
- clicks: z17.number(),
9397
- impressions: z17.number(),
9398
- ctr: z17.number(),
9399
- position: z17.number()
9490
+ const metrics = z18.object({
9491
+ clicks: z18.number(),
9492
+ impressions: z18.number(),
9493
+ ctr: z18.number(),
9494
+ position: z18.number()
9400
9495
  }).passthrough().nullable();
9401
9496
  const validateView = (data) => {
9402
9497
  const record2 = versionedReport.parse(data);
9403
- z17.literal(client.resourceId).parse(record2.resourceId);
9498
+ z18.literal(client.resourceId).parse(record2.resourceId);
9404
9499
  bindingSchema.nullable().parse(record2.binding);
9405
9500
  runSchema.nullable().parse(record2.latest);
9406
9501
  runSchema.nullable().parse(record2.report);
9407
- z17.array(
9408
- z17.object({ key: z17.string(), current: metrics, previous: metrics }).passthrough()
9502
+ z18.array(
9503
+ z18.object({ key: z18.string(), current: metrics, previous: metrics }).passthrough()
9409
9504
  ).parse(record2.rows);
9410
- z17.number().int().nonnegative().parse(record2.total);
9411
- z17.literal(values.dataset ?? "pages").parse(record2.dataset);
9505
+ z18.number().int().nonnegative().parse(record2.total);
9506
+ z18.literal(values.dataset ?? "pages").parse(record2.dataset);
9412
9507
  return record2;
9413
9508
  };
9414
9509
  if (action === "export") {
@@ -9424,9 +9519,9 @@ async function runSeoGscCommand(options) {
9424
9519
  const text = await response.text();
9425
9520
  if (values.format === "json") {
9426
9521
  const report = validateView(JSON.parse(text));
9427
- z17.literal(reportId).parse(runSchema.parse(report.report).id);
9428
- z17.array(z17.unknown()).length(rows).parse(report.rows);
9429
- z17.literal(truncated === "true").parse(report.truncated);
9522
+ z18.literal(reportId).parse(runSchema.parse(report.report).id);
9523
+ z18.array(z18.unknown()).length(rows).parse(report.rows);
9524
+ z18.literal(truncated === "true").parse(report.truncated);
9430
9525
  }
9431
9526
  const output = await writeSeoReport(options, values.output, text);
9432
9527
  return {
@@ -9447,7 +9542,7 @@ async function runSeoGscCommand(options) {
9447
9542
  };
9448
9543
  }
9449
9544
  const record = versionedReport.parse(await response.json());
9450
- if (action === "disconnect") z17.literal(true).parse(record.unbound);
9545
+ if (action === "disconnect") z18.literal(true).parse(record.unbound);
9451
9546
  else if (writing) {
9452
9547
  runSchema.parse(record.run);
9453
9548
  if (action === "bind") {
@@ -9469,8 +9564,8 @@ import { randomUUID as randomUUID7 } from "crypto";
9469
9564
  import { writeFile as writeFile9 } from "fs/promises";
9470
9565
  import path27 from "path";
9471
9566
  import { setTimeout as setTimeout3 } from "timers/promises";
9472
- import { parseArgs as parseArgs7 } from "util";
9473
- import { z as z18 } from "zod";
9567
+ import { parseArgs as parseArgs8 } from "util";
9568
+ import { z as z19 } from "zod";
9474
9569
  var PERFORMANCE_HELP = `
9475
9570
  siteos seo performance run --audit <id> --url <url> [--url <url>...] [--device <mobile|desktop>] [--idempotency-key <key>] [--environment <slug>] [--json]
9476
9571
  siteos seo performance list [--device <mobile|desktop>] [--environment <slug>] [--json]
@@ -9487,7 +9582,7 @@ Reuse the returned idempotency key after an uncertain run response. Wait default
9487
9582
  async function runSeoPerformanceCommand(options) {
9488
9583
  let idempotencyKey;
9489
9584
  try {
9490
- const { values, positionals } = parseArgs7({
9585
+ const { values, positionals } = parseArgs8({
9491
9586
  args: options.args.slice(1),
9492
9587
  strict: true,
9493
9588
  allowPositionals: true,
@@ -9558,12 +9653,12 @@ async function runSeoPerformanceCommand(options) {
9558
9653
  const runtime = commonProjectRuntime(options);
9559
9654
  const writing = ["run", "cancel"].includes(action);
9560
9655
  const scope = writing ? "seo:audits:write" : "seo:workspace:read";
9561
- const batchSchema = z18.object({
9562
- id: z18.string(),
9563
- resourceId: z18.literal(context.resourceId),
9564
- organizationId: z18.literal(context.overview.project.organizationId),
9565
- sourceAuditId: z18.string(),
9566
- state: z18.enum([
9656
+ const batchSchema = z19.object({
9657
+ id: z19.string(),
9658
+ resourceId: z19.literal(context.resourceId),
9659
+ organizationId: z19.literal(context.overview.project.organizationId),
9660
+ sourceAuditId: z19.string(),
9661
+ state: z19.enum([
9567
9662
  "queued",
9568
9663
  "running",
9569
9664
  "completed",
@@ -9571,8 +9666,8 @@ async function runSeoPerformanceCommand(options) {
9571
9666
  "failed",
9572
9667
  "cancelled"
9573
9668
  ]),
9574
- device: z18.enum(["mobile", "desktop"]),
9575
- urls: z18.array(z18.string()).min(1).max(10)
9669
+ device: z19.enum(["mobile", "desktop"]),
9670
+ urls: z19.array(z19.string()).min(1).max(10)
9576
9671
  }).passthrough();
9577
9672
  const query = new URLSearchParams();
9578
9673
  if (values.device) query.set("device", values.device);
@@ -9620,10 +9715,10 @@ async function runSeoPerformanceCommand(options) {
9620
9715
  );
9621
9716
  const text = await response.text();
9622
9717
  if (values.format === "json") {
9623
- const parsed = z18.object({
9624
- contractVersion: z18.literal(1),
9718
+ const parsed = z19.object({
9719
+ contractVersion: z19.literal(1),
9625
9720
  batch: batchSchema,
9626
- pages: z18.array(z18.unknown())
9721
+ pages: z19.array(z19.unknown())
9627
9722
  }).parse(JSON.parse(text));
9628
9723
  if (parsed.batch.id !== id)
9629
9724
  throw new Error(
@@ -9646,8 +9741,8 @@ async function runSeoPerformanceCommand(options) {
9646
9741
  }
9647
9742
  const data = await response.json();
9648
9743
  if (!response.ok) {
9649
- const error = z18.object({
9650
- error: z18.object({ code: z18.string(), message: z18.string().max(500) })
9744
+ const error = z19.object({
9745
+ error: z19.object({ code: z19.string(), message: z19.string().max(500) })
9651
9746
  }).safeParse(data);
9652
9747
  throw new SiteOSAuthApiError({
9653
9748
  code: error.success ? error.data.error.code : "SEO_REQUEST_FAILED",
@@ -9655,8 +9750,8 @@ async function runSeoPerformanceCommand(options) {
9655
9750
  status: response.status
9656
9751
  });
9657
9752
  }
9658
- const record = z18.object({ contractVersion: z18.literal(1) }).passthrough().parse(data);
9659
- if (action === "cancel") z18.literal(true).parse(record.cancelled);
9753
+ const record = z19.object({ contractVersion: z19.literal(1) }).passthrough().parse(data);
9754
+ if (action === "cancel") z19.literal(true).parse(record.cancelled);
9660
9755
  else if (action === "run") {
9661
9756
  const accepted = batchSchema.parse(record.batch);
9662
9757
  const normalize = (url) => {
@@ -9669,8 +9764,8 @@ async function runSeoPerformanceCommand(options) {
9669
9764
  "The queued check does not match the requested source, device and URLs."
9670
9765
  );
9671
9766
  } else {
9672
- z18.literal(context.resourceId).parse(record.resourceId);
9673
- z18.array(batchSchema).parse(record.batches);
9767
+ z19.literal(context.resourceId).parse(record.resourceId);
9768
+ z19.array(batchSchema).parse(record.batches);
9674
9769
  const selected = batchSchema.nullable().parse(record.batch);
9675
9770
  if (id && selected?.id !== id)
9676
9771
  throw new Error(
@@ -9697,7 +9792,7 @@ async function runSeoPerformanceCommand(options) {
9697
9792
  } catch (cause) {
9698
9793
  const error = {
9699
9794
  code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
9700
- message: cause instanceof z18.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The performance command failed."
9795
+ message: cause instanceof z19.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The performance command failed."
9701
9796
  };
9702
9797
  return {
9703
9798
  exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
@@ -9733,7 +9828,8 @@ Usage:
9733
9828
  siteos seo pages [--audit <id>] [--query <text>] [--url <url>] [--page <number>] [--environment <slug>] [--json]
9734
9829
  siteos seo issues [--audit <id>] [--rule <id>] [--page <number>] [--environment <slug>] [--json]
9735
9830
  siteos seo changes [--audit <id>] [--state <new|reopened|still_present|resolved|not_rechecked>] [--page <number>] [--environment <slug>] [--json]
9736
- siteos seo recheck --audit <id> --url <url> [--environment <slug>] [--json]
9831
+ siteos seo repair --audit <id> --url <url> [--url <url>...] [--rule <id>] [--resource <expected-id>] [--environment <slug>] [--json]
9832
+ siteos seo recheck --audit <id> --url <url> [--url <url>...] [--resource <expected-id>] [--environment <slug>] [--json]
9737
9833
  siteos seo issue <ignore|restore> --audit <id> --url <url> --rule <id> --reason <text> --revision <number> [--environment <slug>] [--json]
9738
9834
  siteos seo schedule show [--environment <slug>] [--json]
9739
9835
  siteos seo schedule set --enabled <true|false> --weekday <1-7> --time <HH:mm> --timezone <IANA> --revision <number> [--environment <slug>] [--json]
@@ -9749,7 +9845,9 @@ ${GSC_HELP.trim().split("\n\n")[0]}
9749
9845
  Schedule and notification writes require an owner/admin and the saved revision (initially 0).
9750
9846
  Export writes all matching rows to a new file; existing files are never overwritten.
9751
9847
  Runs are queued. Read audit show until terminal; an accepted run is not a completed check.
9752
- Recheck accepts a URL observed in the source audit. Cross-page rules require a full audit.
9848
+ Repair reads a bounded brief for selected saved evidence; it does not start work.
9849
+ Recheck accepts 1\u201320 URLs observed in the source audit. Cross-page rules require a full audit.
9850
+ --resource verifies the selected environment binding before requesting a brief or recheck.
9753
9851
  Read the current disposition revision before ignore/restore; use 0 if no decision exists.
9754
9852
  Setup: siteos project connect seo. No crawl runs during setup.
9755
9853
  ${PERFORMANCE_HELP.trim().split("\n\n").slice(1).join("\n\n")}
@@ -9762,9 +9860,11 @@ async function runSeoCommand(options) {
9762
9860
  return runSeoPerformanceCommand(options);
9763
9861
  if (options.args[0] === "research") return runSeoResearchCommand(options);
9764
9862
  if (options.args[0] === "gsc") return runSeoGscCommand(options);
9863
+ if (["repair", "recheck"].includes(options.args[0] ?? ""))
9864
+ return runSeoRepairCommand(options);
9765
9865
  const json = options.args.includes("--json");
9766
9866
  try {
9767
- const { positionals, values } = parseArgs8({
9867
+ const { positionals, values } = parseArgs9({
9768
9868
  args: options.args,
9769
9869
  strict: true,
9770
9870
  allowPositionals: true,
@@ -9827,7 +9927,6 @@ async function runSeoCommand(options) {
9827
9927
  pages: { flags: ["audit", "url", "query", "page"], args: 1 },
9828
9928
  issues: { flags: ["audit", "rule", "page"], args: 1 },
9829
9929
  changes: { flags: ["audit", "state", "page"], args: 1 },
9830
- recheck: { flags: ["audit", "url"], args: 1 },
9831
9930
  "issue ignore": {
9832
9931
  flags: ["audit", "url", "rule", "reason", "revision"],
9833
9932
  args: 2
@@ -9844,8 +9943,6 @@ async function runSeoCommand(options) {
9844
9943
  throw new Error(
9845
9944
  "Invalid SEO operation or flags. Run `siteos seo --help`."
9846
9945
  );
9847
- if (action === "recheck" && (!values.audit || !values.url))
9848
- throw new Error("Recheck requires --audit and --url.");
9849
9946
  if (action === "issue" && ["audit", "url", "rule", "reason", "revision"].some(
9850
9947
  (key) => !values[key]
9851
9948
  ))
@@ -9895,13 +9992,9 @@ async function runSeoCommand(options) {
9895
9992
  );
9896
9993
  const runtime = commonProjectRuntime(options);
9897
9994
  const retryNotification = route === "notifications retry";
9898
- const writing = retryNotification || setting || [
9899
- "audit run",
9900
- "audit cancel",
9901
- "recheck",
9902
- "issue ignore",
9903
- "issue restore"
9904
- ].includes(route);
9995
+ const writing = retryNotification || setting || ["audit run", "audit cancel", "issue ignore", "issue restore"].includes(
9996
+ route
9997
+ );
9905
9998
  const scope = retryNotification ? "seo:notifications:write" : setting ? action === "schedule" ? "seo:schedule:write" : "seo:notifications:write" : !writing ? "seo:workspace:read" : action === "issue" ? "seo:issues:write" : "seo:audits:write";
9906
9999
  const grant = await runtime.grants.acquire({
9907
10000
  audience: "siteos-seo",
@@ -9930,7 +10023,7 @@ async function runSeoCommand(options) {
9930
10023
  query.set("state", values.state);
9931
10024
  }
9932
10025
  if (route === "audit show") query.set("audit", positionals[2]);
9933
- const suffix = retryNotification ? "/notification-retries" : automation ? setting ? `/${action}` : route === "notifications destinations" ? "/destinations" : "/automation" : action === "export" ? `/export?${query}` : route === "audit run" ? "/audits" : route === "audit cancel" ? `/audits/${encodeURIComponent(positionals[2])}/cancel` : action === "recheck" ? "/rechecks" : action === "issue" ? "/dispositions" : `?${query}`;
10026
+ const suffix = retryNotification ? "/notification-retries" : automation ? setting ? `/${action}` : route === "notifications destinations" ? "/destinations" : "/automation" : action === "export" ? `/export?${query}` : route === "audit run" ? "/audits" : route === "audit cancel" ? `/audits/${encodeURIComponent(positionals[2])}/cancel` : action === "issue" ? "/dispositions" : `?${query}`;
9934
10027
  const body = retryNotification ? { notificationId: positionals[2] } : route === "schedule set" ? {
9935
10028
  enabled: values.enabled === "true",
9936
10029
  weekday: Number(values.weekday),
@@ -9943,7 +10036,7 @@ async function runSeoCommand(options) {
9943
10036
  minimumSeverity: values.severity,
9944
10037
  includeFailures: values.failures === "true",
9945
10038
  expectedRevision: Number(values.revision)
9946
- } : action === "recheck" ? { auditId: values.audit, urls: [values.url] } : action === "issue" ? {
10039
+ } : action === "issue" ? {
9947
10040
  auditId: values.audit,
9948
10041
  url: values.url,
9949
10042
  ruleId: values.rule,
@@ -9977,15 +10070,15 @@ async function runSeoCommand(options) {
9977
10070
  );
9978
10071
  const text = await response.text();
9979
10072
  if (values.format === "json")
9980
- z19.object({
9981
- contractVersion: z19.literal(1),
9982
- audit: z19.object({
9983
- id: z19.literal(values.audit),
9984
- resourceId: z19.literal(context.resourceId)
10073
+ z20.object({
10074
+ contractVersion: z20.literal(1),
10075
+ audit: z20.object({
10076
+ id: z20.literal(values.audit),
10077
+ resourceId: z20.literal(context.resourceId)
9985
10078
  }),
9986
- kind: z19.literal(values.kind),
9987
- totalRows: z19.literal(rows),
9988
- rows: z19.array(z19.unknown()).length(rows)
10079
+ kind: z20.literal(values.kind),
10080
+ totalRows: z20.literal(rows),
10081
+ rows: z20.array(z20.unknown()).length(rows)
9989
10082
  }).parse(JSON.parse(text));
9990
10083
  const output = path28.resolve(options.cwd ?? process.cwd(), values.output);
9991
10084
  await writeFile10(output, text, { flag: "wx", mode: 384 });
@@ -10006,8 +10099,8 @@ async function runSeoCommand(options) {
10006
10099
  }
10007
10100
  const data = await response.json();
10008
10101
  if (!response.ok) {
10009
- const result = z19.object({
10010
- error: z19.object({ code: z19.string(), message: z19.string().max(500) })
10102
+ const result = z20.object({
10103
+ error: z20.object({ code: z20.string(), message: z20.string().max(500) })
10011
10104
  }).safeParse(data);
10012
10105
  throw new SiteOSAuthApiError({
10013
10106
  code: result.success ? result.data.error.code : "SEO_REQUEST_FAILED",
@@ -10015,34 +10108,34 @@ async function runSeoCommand(options) {
10015
10108
  status: response.status
10016
10109
  });
10017
10110
  }
10018
- const record = z19.object({ contractVersion: z19.literal(1) }).passthrough().parse(data);
10111
+ const record = z20.object({ contractVersion: z20.literal(1) }).passthrough().parse(data);
10019
10112
  if (automation) {
10020
- z19.literal(context.resourceId).parse(record.resourceId);
10021
- const schedule = z19.object({
10022
- enabled: z19.boolean(),
10023
- weekday: z19.number().int().min(1).max(7),
10024
- time: z19.string(),
10025
- timeZone: z19.string(),
10026
- revision: z19.number().int().min(0),
10027
- nextRunAt: z19.string().nullable()
10113
+ z20.literal(context.resourceId).parse(record.resourceId);
10114
+ const schedule = z20.object({
10115
+ enabled: z20.boolean(),
10116
+ weekday: z20.number().int().min(1).max(7),
10117
+ time: z20.string(),
10118
+ timeZone: z20.string(),
10119
+ revision: z20.number().int().min(0),
10120
+ nextRunAt: z20.string().nullable()
10028
10121
  });
10029
- const notificationRoute = z19.object({
10030
- enabled: z19.boolean(),
10031
- minimumSeverity: z19.enum(["error", "warning"]),
10032
- includeFailures: z19.boolean(),
10033
- revision: z19.number().int().min(0),
10034
- destinationId: z19.string().nullable()
10122
+ const notificationRoute = z20.object({
10123
+ enabled: z20.boolean(),
10124
+ minimumSeverity: z20.enum(["error", "warning"]),
10125
+ includeFailures: z20.boolean(),
10126
+ revision: z20.number().int().min(0),
10127
+ destinationId: z20.string().nullable()
10035
10128
  });
10036
10129
  if (retryNotification) {
10037
- z19.literal(true).parse(record.retryQueued);
10038
- z19.literal(positionals[2]).parse(record.notificationId);
10130
+ z20.literal(true).parse(record.retryQueued);
10131
+ z20.literal(positionals[2]).parse(record.notificationId);
10039
10132
  } else if (route === "notifications destinations")
10040
- z19.object({
10041
- candidates: z19.array(
10042
- z19.object({
10043
- candidateId: z19.string(),
10044
- label: z19.string(),
10045
- availability: z19.literal("available")
10133
+ z20.object({
10134
+ candidates: z20.array(
10135
+ z20.object({
10136
+ candidateId: z20.string(),
10137
+ label: z20.string(),
10138
+ availability: z20.literal("available")
10046
10139
  })
10047
10140
  )
10048
10141
  }).parse(record);
@@ -10055,46 +10148,46 @@ async function runSeoCommand(options) {
10055
10148
  notificationRoute.parse(record.route);
10056
10149
  }
10057
10150
  } else if (!writing) {
10058
- const validated = z19.object({
10059
- resource: z19.object({
10060
- id: z19.literal(context.resourceId),
10061
- organizationId: z19.literal(context.overview.project.organizationId)
10151
+ const validated = z20.object({
10152
+ resource: z20.object({
10153
+ id: z20.literal(context.resourceId),
10154
+ organizationId: z20.literal(context.overview.project.organizationId)
10062
10155
  }),
10063
- audits: z19.array(z19.object({ id: z19.string() }).passthrough()),
10064
- audit: z19.object({
10065
- id: z19.string(),
10066
- resourceId: z19.literal(context.resourceId)
10156
+ audits: z20.array(z20.object({ id: z20.string() }).passthrough()),
10157
+ audit: z20.object({
10158
+ id: z20.string(),
10159
+ resourceId: z20.literal(context.resourceId)
10067
10160
  }).passthrough().nullable(),
10068
- pages: z19.array(z19.unknown()),
10069
- issues: z19.array(z19.unknown()),
10070
- changes: z19.array(z19.unknown()),
10071
- totalChanges: z19.number(),
10072
- dispositions: z19.array(z19.unknown())
10161
+ pages: z20.array(z20.unknown()),
10162
+ issues: z20.array(z20.unknown()),
10163
+ changes: z20.array(z20.unknown()),
10164
+ totalChanges: z20.number(),
10165
+ dispositions: z20.array(z20.unknown())
10073
10166
  }).passthrough().parse(record);
10074
10167
  const selected = query.get("audit");
10075
10168
  if (selected && validated.audit?.id !== selected)
10076
10169
  throw new Error("The SEO response does not match the requested audit.");
10077
10170
  } else if (record.audit)
10078
- z19.object({
10079
- id: z19.string(),
10080
- resourceId: z19.literal(context.resourceId),
10081
- organizationId: z19.literal(context.overview.project.organizationId),
10082
- state: z19.literal("queued")
10171
+ z20.object({
10172
+ id: z20.string(),
10173
+ resourceId: z20.literal(context.resourceId),
10174
+ organizationId: z20.literal(context.overview.project.organizationId),
10175
+ state: z20.literal("queued")
10083
10176
  }).parse(record.audit);
10084
- else if (route === "audit cancel") z19.literal(true).parse(record.cancelled);
10177
+ else if (route === "audit cancel") z20.literal(true).parse(record.cancelled);
10085
10178
  else if (action === "issue")
10086
- z19.object({
10087
- url: z19.literal(values.url),
10088
- ruleId: z19.literal(values.rule),
10089
- ignored: z19.literal(positionals[1] === "ignore"),
10090
- revision: z19.literal(Number(values.revision) + 1)
10179
+ z20.object({
10180
+ url: z20.literal(values.url),
10181
+ ruleId: z20.literal(values.rule),
10182
+ ignored: z20.literal(positionals[1] === "ignore"),
10183
+ revision: z20.literal(Number(values.revision) + 1)
10091
10184
  }).parse(record.disposition);
10092
10185
  else throw new Error("The SEO service returned an invalid response.");
10093
10186
  return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
10094
10187
  } catch (cause) {
10095
10188
  const error = {
10096
10189
  code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
10097
- message: cause instanceof z19.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
10190
+ message: cause instanceof z20.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
10098
10191
  };
10099
10192
  return {
10100
10193
  exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,