@canonry/canonry 4.158.0 → 4.160.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.
Files changed (53) hide show
  1. package/README.md +3 -3
  2. package/THIRD_PARTY_NOTICES.md +102 -0
  3. package/assets/THIRD_PARTY_NOTICES.md +102 -0
  4. package/assets/agent-workspace/skills/aero/SKILL.md +16 -1
  5. package/assets/agent-workspace/skills/canonry/SKILL.md +17 -5
  6. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +67 -0
  7. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +231 -62
  8. package/assets/assets/{AuditHistoryPanel-McQfjoU9.js → AuditHistoryPanel-AtgoG2AH.js} +1 -1
  9. package/assets/assets/{BacklinksPage-Da8y1Wv8.js → BacklinksPage-DrOSF7Mu.js} +1 -1
  10. package/assets/assets/{HistoryPage-9MJfhRyT.js → HistoryPage-CGQXleJc.js} +1 -1
  11. package/assets/assets/{MeasurementPropertyPage-BiNAHEJW.js → MeasurementPropertyPage-C7D2OgIQ.js} +1 -1
  12. package/assets/assets/ProjectPage-Lp1VjNvL.js +12 -0
  13. package/assets/assets/{RunRow-DCG2LBlS.js → RunRow-BdzKKODo.js} +1 -1
  14. package/assets/assets/{RunsPage-ByWA_FSk.js → RunsPage-BU96L07S.js} +1 -1
  15. package/assets/assets/{SettingsPage-DdSeKAKF.js → SettingsPage-LEca0kDH.js} +1 -1
  16. package/assets/assets/SiteHealthSection-CKM4DUfE.js +4 -0
  17. package/assets/assets/{TrafficPage-5u8rPcBt.js → TrafficPage-tU9wl1F_.js} +1 -1
  18. package/assets/assets/{TrafficSourceDetailPage-Dm-fK87u.js → TrafficSourceDetailPage-DZTpnU9f.js} +1 -1
  19. package/assets/assets/extract-error-message-BAyN-ftk.js +1 -0
  20. package/assets/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  21. package/assets/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  22. package/assets/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  23. package/assets/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  24. package/assets/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  25. package/assets/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  26. package/assets/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  27. package/assets/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  28. package/assets/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  29. package/assets/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  30. package/assets/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  31. package/assets/assets/index-CACRaOmZ.css +1 -0
  32. package/assets/assets/index-Wqv7kvs-.js +83 -0
  33. package/assets/assets/{react-sigma_core.esm.min-ComIyDKi.js → react-sigma_core.esm.min-DlZOAc_w.js} +2 -2
  34. package/assets/assets/{vendor-lucide-BtYdosYk.js → vendor-lucide-nYKv-BB7.js} +1 -1
  35. package/assets/assets/{vendor-markdown-D274_wsI.js → vendor-markdown-Bn7fRwrx.js} +1 -1
  36. package/assets/assets/{vendor-radix-BxMXSwbC.js → vendor-radix-4UOaVWza.js} +1 -1
  37. package/assets/assets/{vendor-recharts-C4ktw_aE.js → vendor-recharts-D8rt5lKn.js} +1 -1
  38. package/assets/assets/{vendor-tanstack-pFxw0eGP.js → vendor-tanstack-Dock0eH1.js} +1 -1
  39. package/assets/index.html +7 -7
  40. package/dist/{chunk-DEVAEC2S.js → chunk-7M7T5QXS.js} +290 -16
  41. package/dist/{chunk-LK6YFHG4.js → chunk-HQAVEK42.js} +65 -15
  42. package/dist/{chunk-YP4K2KXH.js → chunk-X7CYY5Q7.js} +2687 -686
  43. package/dist/{chunk-4VZP64GZ.js → chunk-XGESWHFP.js} +660 -274
  44. package/dist/cli.js +333 -117
  45. package/dist/index.d.ts +37 -14
  46. package/dist/index.js +4 -4
  47. package/dist/{intelligence-service-LQ6ZRWMF.js → intelligence-service-6XQMM466.js} +2 -2
  48. package/dist/mcp.js +2 -2
  49. package/package.json +11 -9
  50. package/assets/assets/ProjectPage-mWSMjXrR.js +0 -15
  51. package/assets/assets/extract-error-message-CtxkAd6E.js +0 -1
  52. package/assets/assets/index-DUfg50Pl.js +0 -83
  53. package/assets/assets/index-cXb3mfeQ.css +0 -1
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  detectAndTrackUpgrade,
17
17
  emitInstallSummary,
18
18
  formatAuditFactorScore,
19
- getCloudflareTrafficConnection,
19
+ getCloudflareTrafficConnectionBySourceId,
20
20
  getMissingUserSkillsNudge,
21
21
  getOrCreateAnonymousId,
22
22
  installSkills,
@@ -31,7 +31,7 @@ import {
31
31
  showFirstRunNotice,
32
32
  trackCliCommandFinished,
33
33
  trackEvent
34
- } from "./chunk-4VZP64GZ.js";
34
+ } from "./chunk-XGESWHFP.js";
35
35
  import {
36
36
  CliError,
37
37
  EXIT_SYSTEM_ERROR,
@@ -51,7 +51,7 @@ import {
51
51
  saveConfigPatch,
52
52
  systemError,
53
53
  usageError
54
- } from "./chunk-LK6YFHG4.js";
54
+ } from "./chunk-HQAVEK42.js";
55
55
  import {
56
56
  CLOUDFLARE_WORKER_BINDINGS,
57
57
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -65,7 +65,7 @@ import {
65
65
  projects,
66
66
  queries,
67
67
  renderReportHtml
68
- } from "./chunk-YP4K2KXH.js";
68
+ } from "./chunk-X7CYY5Q7.js";
69
69
  import {
70
70
  AdsDeliverySnapshotStatuses,
71
71
  AdsHistoricalCampaignRollupStatuses,
@@ -129,7 +129,7 @@ import {
129
129
  providerQuotaPolicySchema,
130
130
  resolveProviderInput,
131
131
  winnabilityClassSchema
132
- } from "./chunk-DEVAEC2S.js";
132
+ } from "./chunk-7M7T5QXS.js";
133
133
 
134
134
  // src/cli.ts
135
135
  import { pathToFileURL } from "url";
@@ -3875,9 +3875,9 @@ async function gaConnect(project, opts) {
3875
3875
  propertyId: opts.propertyId
3876
3876
  };
3877
3877
  if (opts.keyFile) {
3878
- const fs18 = await import("fs");
3878
+ const fs19 = await import("fs");
3879
3879
  try {
3880
- const content = fs18.readFileSync(opts.keyFile, "utf-8");
3880
+ const content = fs19.readFileSync(opts.keyFile, "utf-8");
3881
3881
  JSON.parse(content);
3882
3882
  body.keyJson = content;
3883
3883
  } catch (e) {
@@ -5292,6 +5292,9 @@ var GET_CLI_COMMANDS = [
5292
5292
  }
5293
5293
  ];
5294
5294
 
5295
+ // src/commands/traffic.ts
5296
+ import fs5 from "fs";
5297
+
5295
5298
  // src/cloudflare-worker-deploy.ts
5296
5299
  import { spawn } from "child_process";
5297
5300
  import { randomUUID } from "crypto";
@@ -5455,7 +5458,8 @@ function writeCloudflareWorkerArtifacts(artifacts, contents) {
5455
5458
  }
5456
5459
  function assertCloudflareArtifactsDoNotContainSecrets(contents, secrets) {
5457
5460
  const artifactText = [contents.workerScript, contents.wranglerToml, contents.instructions ?? ""];
5458
- for (const secret of [secrets.bearerToken, secrets.hmacSecret]) {
5461
+ const secretValues = "apiToken" in secrets ? [secrets.apiToken] : [secrets.bearerToken, secrets.hmacSecret];
5462
+ for (const secret of secretValues) {
5459
5463
  if (secret && artifactText.some((value) => value.includes(secret))) {
5460
5464
  throw new Error("Cloudflare setup response unexpectedly contained a cleartext secret");
5461
5465
  }
@@ -5463,7 +5467,8 @@ function assertCloudflareArtifactsDoNotContainSecrets(contents, secrets) {
5463
5467
  }
5464
5468
  function redactCloudflareSecrets(message, secrets) {
5465
5469
  let redacted = message;
5466
- for (const secret of [secrets.bearerToken, secrets.hmacSecret]) {
5470
+ const secretValues = "apiToken" in secrets ? [secrets.apiToken] : [secrets.bearerToken, secrets.hmacSecret];
5471
+ for (const secret of secretValues) {
5467
5472
  if (secret) redacted = redacted.replaceAll(secret, "[REDACTED]");
5468
5473
  }
5469
5474
  return redacted;
@@ -5520,12 +5525,14 @@ ${diagnostic}` : "";
5520
5525
  }
5521
5526
  async function preflightCloudflareWrangler(opts = {}) {
5522
5527
  const runner = opts.run ?? runWranglerCapture;
5528
+ const deliveryMode = opts.deliveryMode ?? "direct-push";
5523
5529
  const help = await runner(
5524
5530
  "wrangler",
5525
5531
  ["deploy", "--help"],
5526
5532
  { cwd: path2.resolve(opts.cwd ?? process.cwd()) }
5527
5533
  );
5528
- const missing = ["--dry-run", "--secrets-file", "--strict"].filter((flag) => !help.includes(flag));
5534
+ const requiredFlags = deliveryMode === "direct-push" ? ["--dry-run", "--secrets-file", "--strict"] : ["--dry-run", "--strict"];
5535
+ const missing = requiredFlags.filter((flag) => !help.includes(flag));
5529
5536
  if (missing.length > 0) {
5530
5537
  throw new Error(`Wrangler deploy does not support required flags: ${missing.join(", ")}`);
5531
5538
  }
@@ -5539,29 +5546,38 @@ async function preflightCloudflareWrangler(opts = {}) {
5539
5546
  try {
5540
5547
  fs4.chmodSync(tempDirectory, 448);
5541
5548
  fs4.writeFileSync(workerScriptPath, generateWorkerScript({
5542
- deliveryMode: "direct-push",
5549
+ deliveryMode,
5543
5550
  workerVersion: "preflight",
5544
5551
  botList: DEFAULT_BOT_LIST
5545
5552
  }), { encoding: "utf-8", flag: "wx", mode: 384 });
5546
- fs4.writeFileSync(wranglerTomlPath, generateWranglerToml({
5547
- deliveryMode: "direct-push",
5548
- sourceId: "preflight",
5549
- hostname: "example.invalid",
5550
- ingestUrl: "https://canonry.example.invalid/api/v1/projects/preflight/traffic/cloudflare/ingest",
5551
- workerVersion: "preflight"
5552
- }), { encoding: "utf-8", flag: "wx", mode: 384 });
5553
- fs4.writeFileSync(secretsPath, JSON.stringify({
5554
- [CLOUDFLARE_WORKER_BINDINGS.bearerToken]: "preflight-bearer",
5555
- [CLOUDFLARE_WORKER_BINDINGS.hmacSecret]: "preflight-hmac"
5556
- }), { encoding: "utf-8", flag: "wx", mode: 384 });
5553
+ fs4.writeFileSync(wranglerTomlPath, generateWranglerToml(
5554
+ deliveryMode === "queue-pull" ? {
5555
+ deliveryMode,
5556
+ sourceId: "preflight",
5557
+ hostname: "example.invalid",
5558
+ queueName: "canonry-preflight",
5559
+ workerVersion: "preflight"
5560
+ } : {
5561
+ deliveryMode,
5562
+ sourceId: "preflight",
5563
+ hostname: "example.invalid",
5564
+ ingestUrl: "https://canonry.example.invalid/api/v1/projects/preflight/traffic/cloudflare/ingest",
5565
+ workerVersion: "preflight"
5566
+ }
5567
+ ), { encoding: "utf-8", flag: "wx", mode: 384 });
5568
+ if (deliveryMode === "direct-push") {
5569
+ fs4.writeFileSync(secretsPath, JSON.stringify({
5570
+ [CLOUDFLARE_WORKER_BINDINGS.bearerToken]: "preflight-bearer",
5571
+ [CLOUDFLARE_WORKER_BINDINGS.hmacSecret]: "preflight-hmac"
5572
+ }), { encoding: "utf-8", flag: "wx", mode: 384 });
5573
+ }
5557
5574
  const validationOutput = await runner(
5558
5575
  "wrangler",
5559
5576
  [
5560
5577
  "deploy",
5561
5578
  "--config",
5562
5579
  wranglerTomlPath,
5563
- "--secrets-file",
5564
- secretsPath,
5580
+ ...deliveryMode === "direct-push" ? ["--secrets-file", secretsPath] : [],
5565
5581
  "--strict",
5566
5582
  "--dry-run",
5567
5583
  "--outdir",
@@ -5582,6 +5598,18 @@ ${wranglerDiagnostic(validationOutput)}`
5582
5598
  }
5583
5599
  }
5584
5600
  async function deployCloudflareWorker(opts) {
5601
+ const deliveryMode = opts.deliveryMode ?? "direct-push";
5602
+ if (deliveryMode === "queue-pull") {
5603
+ const runner = opts.run ?? runWrangler;
5604
+ await runner(
5605
+ "wrangler",
5606
+ ["deploy", "--config", path2.resolve(opts.wranglerTomlPath), "--strict"],
5607
+ { cwd: path2.dirname(path2.resolve(opts.wranglerTomlPath)) }
5608
+ );
5609
+ return;
5610
+ }
5611
+ if (!opts.secrets) throw new Error("Direct-push Worker deploy requires bearer and HMAC secrets");
5612
+ const secrets = opts.secrets;
5585
5613
  const tempDirectory = fs4.mkdtempSync(path2.join(opts.tempRoot ?? os.tmpdir(), "canonry-cloudflare-secrets-"));
5586
5614
  const secretsPath = path2.join(tempDirectory, "secrets.json");
5587
5615
  try {
@@ -5589,8 +5617,8 @@ async function deployCloudflareWorker(opts) {
5589
5617
  fs4.writeFileSync(
5590
5618
  secretsPath,
5591
5619
  JSON.stringify({
5592
- [CLOUDFLARE_WORKER_BINDINGS.bearerToken]: opts.secrets.bearerToken,
5593
- [CLOUDFLARE_WORKER_BINDINGS.hmacSecret]: opts.secrets.hmacSecret
5620
+ [CLOUDFLARE_WORKER_BINDINGS.bearerToken]: secrets.bearerToken,
5621
+ [CLOUDFLARE_WORKER_BINDINGS.hmacSecret]: secrets.hmacSecret
5594
5622
  }),
5595
5623
  { encoding: "utf-8", flag: "wx", mode: 384 }
5596
5624
  );
@@ -5618,6 +5646,9 @@ async function deployCloudflareWorker(opts) {
5618
5646
  }
5619
5647
 
5620
5648
  // src/commands/traffic.ts
5649
+ var DEFAULT_CLOUDFLARE_QUEUE_RETENTION_SECONDS = 345600;
5650
+ var CLOUDFLARE_QUEUE_RETENTION_MIN_SECONDS = 60;
5651
+ var CLOUDFLARE_QUEUE_RETENTION_MAX_SECONDS = 1209600;
5621
5652
  function getClient9() {
5622
5653
  return createApiClient();
5623
5654
  }
@@ -5627,7 +5658,60 @@ function configString(value, fallback = "(unset)") {
5627
5658
  function errorMessage2(error) {
5628
5659
  return error instanceof Error ? error.message : String(error);
5629
5660
  }
5661
+ function requireCloudflareQueueOption(project, value, option) {
5662
+ if (value?.trim()) return value.trim();
5663
+ const code = option === "--account-id" ? "TRAFFIC_CLOUDFLARE_QUEUE_ACCOUNT_REQUIRED" : option === "--queue-id" ? "TRAFFIC_CLOUDFLARE_QUEUE_ID_REQUIRED" : option === "--queue-name" ? "TRAFFIC_CLOUDFLARE_QUEUE_NAME_REQUIRED" : "TRAFFIC_CLOUDFLARE_QUEUE_TOKEN_FILE_REQUIRED";
5664
+ throw new CliError({
5665
+ code,
5666
+ message: `${option} is required with --delivery-mode queue-pull`,
5667
+ displayMessage: `Error: ${option} is required with --delivery-mode queue-pull`,
5668
+ details: { project }
5669
+ });
5670
+ }
5671
+ function readCloudflareQueueApiToken(project, tokenFile) {
5672
+ try {
5673
+ const stat = fs5.lstatSync(tokenFile);
5674
+ if (!stat.isFile() || stat.isSymbolicLink()) {
5675
+ throw new Error("not a regular file");
5676
+ }
5677
+ const apiToken = fs5.readFileSync(tokenFile, "utf-8").trim();
5678
+ if (!apiToken) throw new Error("file is empty");
5679
+ return apiToken;
5680
+ } catch (error) {
5681
+ const message = errorMessage2(error);
5682
+ throw new CliError({
5683
+ code: "TRAFFIC_CLOUDFLARE_QUEUE_TOKEN_FILE_READ_ERROR",
5684
+ message: `Failed to read --api-token-file: ${message}`,
5685
+ displayMessage: `Error: failed to read --api-token-file "${tokenFile}": ${message}`,
5686
+ details: { project, tokenFile }
5687
+ });
5688
+ }
5689
+ }
5630
5690
  async function trafficConnectCloudflare(project, opts, deps = {}) {
5691
+ const deliveryMode = opts.deliveryMode ?? "direct-push";
5692
+ let queuePull;
5693
+ if (deliveryMode === "queue-pull") {
5694
+ const accountId = requireCloudflareQueueOption(project, opts.accountId, "--account-id");
5695
+ const queueId = requireCloudflareQueueOption(project, opts.queueId, "--queue-id");
5696
+ const queueName = requireCloudflareQueueOption(project, opts.queueName, "--queue-name");
5697
+ const tokenFile = requireCloudflareQueueOption(project, opts.apiTokenFile, "--api-token-file");
5698
+ const retentionSeconds = opts.retentionSeconds ?? DEFAULT_CLOUDFLARE_QUEUE_RETENTION_SECONDS;
5699
+ if (!Number.isInteger(retentionSeconds) || retentionSeconds < CLOUDFLARE_QUEUE_RETENTION_MIN_SECONDS || retentionSeconds > CLOUDFLARE_QUEUE_RETENTION_MAX_SECONDS) {
5700
+ throw new CliError({
5701
+ code: "TRAFFIC_CLOUDFLARE_QUEUE_RETENTION_INVALID",
5702
+ message: "--retention-seconds must be an integer between 60 and 1209600",
5703
+ displayMessage: "Error: --retention-seconds must be an integer between 60 and 1209600",
5704
+ details: { project }
5705
+ });
5706
+ }
5707
+ queuePull = {
5708
+ accountId,
5709
+ queueId,
5710
+ queueName,
5711
+ apiToken: readCloudflareQueueApiToken(project, tokenFile),
5712
+ retentionSeconds
5713
+ };
5714
+ }
5631
5715
  if (opts.deploy && !opts.zoneId) {
5632
5716
  throw new CliError({
5633
5717
  code: "TRAFFIC_CLOUDFLARE_DEPLOY_ZONE_REQUIRED",
@@ -5654,7 +5738,7 @@ async function trafficConnectCloudflare(project, opts, deps = {}) {
5654
5738
  }
5655
5739
  if (opts.deploy) {
5656
5740
  try {
5657
- await (deps.preflightWrangler ?? preflightCloudflareWrangler)();
5741
+ await (deps.preflightWrangler ?? preflightCloudflareWrangler)({ deliveryMode });
5658
5742
  } catch (error) {
5659
5743
  const message = errorMessage2(error);
5660
5744
  throw new CliError({
@@ -5680,18 +5764,27 @@ ${message}`,
5680
5764
  });
5681
5765
  }
5682
5766
  const client = deps.client ?? getClient9();
5683
- const result = await client.trafficConnectCloudflare(project, {
5684
- deliveryMode: "direct-push",
5685
- displayName: opts.displayName,
5686
- zoneId: opts.zoneId,
5687
- accountId: opts.accountId
5688
- });
5767
+ const result = await client.trafficConnectCloudflare(
5768
+ project,
5769
+ deliveryMode === "queue-pull" ? {
5770
+ deliveryMode,
5771
+ displayName: opts.displayName,
5772
+ zoneId: opts.zoneId,
5773
+ ...queuePull
5774
+ } : {
5775
+ deliveryMode,
5776
+ displayName: opts.displayName,
5777
+ zoneId: opts.zoneId,
5778
+ accountId: opts.accountId
5779
+ }
5780
+ );
5689
5781
  const localConfig = (deps.loadLocalConfig ?? loadConfigRaw)();
5690
- const localCredential = localConfig ? getCloudflareTrafficConnection(localConfig, project) : void 0;
5691
- const deploymentCredential = localCredential?.sourceId === result.sourceId ? localCredential : void 0;
5692
- if (deploymentCredential) {
5782
+ const localCredential = localConfig ? getCloudflareTrafficConnectionBySourceId(localConfig, result.sourceId) : void 0;
5783
+ const deploymentCredential = localCredential?.deliveryMode === result.deliveryMode ? localCredential : void 0;
5784
+ const artifactSecrets = deploymentCredential ?? (queuePull ? { apiToken: queuePull.apiToken } : void 0);
5785
+ if (artifactSecrets) {
5693
5786
  try {
5694
- assertCloudflareArtifactsDoNotContainSecrets(result, deploymentCredential);
5787
+ assertCloudflareArtifactsDoNotContainSecrets(result, artifactSecrets);
5695
5788
  } catch {
5696
5789
  throw new CliError({
5697
5790
  code: "TRAFFIC_CLOUDFLARE_SECRET_EXPOSURE",
@@ -5712,7 +5805,7 @@ ${message}`,
5712
5805
  details: { project, sourceId: result.sourceId, outputDirectory }
5713
5806
  });
5714
5807
  }
5715
- if (opts.deploy && !deploymentCredential) {
5808
+ if (opts.deploy && result.deliveryMode === "direct-push" && !deploymentCredential) {
5716
5809
  throw new CliError({
5717
5810
  code: "TRAFFIC_CLOUDFLARE_DEPLOY_CONFIG_UNSHARED",
5718
5811
  message: "Cloudflare auto-deploy requires the CLI and Canonry server to share the same local config",
@@ -5724,7 +5817,7 @@ ${message}`,
5724
5817
  }
5725
5818
  });
5726
5819
  }
5727
- if (opts.deploy && deploymentCredential) {
5820
+ if (opts.deploy && result.deliveryMode === "direct-push" && deploymentCredential?.deliveryMode === "direct-push") {
5728
5821
  try {
5729
5822
  await (deps.deployWorker ?? deployCloudflareWorker)({
5730
5823
  wranglerTomlPath: artifacts.wranglerTomlPath,
@@ -5740,16 +5833,40 @@ ${message}`,
5740
5833
  });
5741
5834
  }
5742
5835
  }
5836
+ if (opts.deploy && result.deliveryMode === "queue-pull") {
5837
+ try {
5838
+ await (deps.deployWorker ?? deployCloudflareWorker)({
5839
+ wranglerTomlPath: artifacts.wranglerTomlPath,
5840
+ deliveryMode: "queue-pull"
5841
+ });
5842
+ } catch (error) {
5843
+ const message = artifactSecrets ? redactCloudflareSecrets(errorMessage2(error), artifactSecrets) : errorMessage2(error);
5844
+ throw new CliError({
5845
+ code: "TRAFFIC_CLOUDFLARE_DEPLOY_FAILED",
5846
+ message,
5847
+ displayMessage: `Error: Cloudflare source connected and artifacts written, but Wrangler deploy failed: ${message}`,
5848
+ details: { project, sourceId: result.sourceId, outputDirectory }
5849
+ });
5850
+ }
5851
+ }
5743
5852
  const summary = {
5744
5853
  sourceId: result.sourceId,
5745
5854
  deliveryMode: result.deliveryMode,
5855
+ activationRequired: result.activationRequired === true,
5746
5856
  workerVersion: result.workerVersion,
5747
5857
  outputDirectory: artifacts.outputDirectory,
5748
5858
  files: {
5749
5859
  workerScript: artifacts.workerScriptPath,
5750
5860
  wranglerConfig: artifacts.wranglerTomlPath
5751
5861
  },
5752
- autoDeployAvailable: Boolean(deploymentCredential),
5862
+ autoDeployAvailable: result.deliveryMode === "queue-pull" || Boolean(deploymentCredential),
5863
+ ...result.deliveryMode === "queue-pull" ? {
5864
+ queuePullConsumer: {
5865
+ required: true,
5866
+ queueName: result.queueName,
5867
+ configuredBy: "wrangler-or-cloudflare-dashboard"
5868
+ }
5869
+ } : {},
5753
5870
  deployment: opts.deploy ? "worker-deployed-route-unattached" : "not-requested",
5754
5871
  routeAttachment: "required-manual",
5755
5872
  requestLimitFailureMode: {
@@ -5764,7 +5881,7 @@ ${message}`,
5764
5881
  console.log(JSON.stringify(summary, null, 2));
5765
5882
  return;
5766
5883
  }
5767
- console.log(`Cloudflare direct-push traffic source connected for project "${project}".`);
5884
+ console.log(`Cloudflare ${result.deliveryMode} traffic source connected for project "${project}".`);
5768
5885
  console.log(` Source ID: ${result.sourceId}`);
5769
5886
  console.log(` Worker version: ${result.workerVersion}`);
5770
5887
  console.log(` Worker script: ${artifacts.workerScriptPath}`);
@@ -5776,17 +5893,42 @@ ${message}`,
5776
5893
  console.log("Required: set the route Request limit failure mode to Fail open before route activation.");
5777
5894
  console.log("Wrangler cannot configure this toggle. Workers Free includes 100,000 requests per day for the account.");
5778
5895
  console.log("Use one authoritative server-traffic source for this site. Overlapping adapters double-count project totals.");
5896
+ if (result.deliveryMode === "queue-pull") {
5897
+ console.log(`Required: enable HTTP pull with \`wrangler queues consumer http add ${result.queueName}\` before the first sync.`);
5898
+ }
5899
+ if (result.activationRequired) {
5900
+ console.log(`Activation required: run \`canonry traffic activate ${project} --source ${result.sourceId}\`. This command does not activate delivery.`);
5901
+ }
5779
5902
  if (opts.deploy) {
5780
5903
  console.log("");
5781
5904
  console.log("Required Cloudflare Dashboard steps:");
5782
5905
  console.log(result.instructions);
5783
- console.log("Keep the Canonry public HTTPS URL reachable so the Worker can push events.");
5906
+ if (result.deliveryMode === "direct-push") {
5907
+ console.log("Keep the Canonry public HTTPS URL reachable so the Worker can push events.");
5908
+ }
5784
5909
  } else if (deploymentCredential) {
5785
5910
  console.log("Next: install and authenticate Wrangler. Inspect the route, then rerun with --deploy --confirm-route --confirm-fail-open and --zone-id <id>.");
5911
+ } else if (result.deliveryMode === "queue-pull") {
5912
+ console.log("Next: install and authenticate Wrangler externally, then rerun with --deploy --confirm-route --confirm-fail-open and --zone-id <id>.");
5786
5913
  } else {
5787
5914
  console.log("Auto-deploy is unavailable because this CLI and the Canonry server do not share local config. Ask the server operator to deploy and install the Worker secrets.");
5788
5915
  }
5789
5916
  }
5917
+ async function trafficActivate(project, opts, deps = {}) {
5918
+ const client = deps.client ?? getClient9();
5919
+ const source = await client.trafficActivate(project, opts.sourceId);
5920
+ if (isMachineFormat(opts.format)) {
5921
+ console.log(JSON.stringify(source, null, 2));
5922
+ return;
5923
+ }
5924
+ const sourceMode = typeof source.config.deliveryMode === "string" ? source.config.deliveryMode : source.sourceType;
5925
+ const trafficSync2 = sourceMode === "direct-push" ? "not required for direct-push delivery" : "enabled for this pull source";
5926
+ console.log(`Traffic source activated for project "${project}".`);
5927
+ console.log(` Source ID: ${source.id}`);
5928
+ console.log(` Source mode: ${sourceMode}`);
5929
+ console.log(` Status: ${source.status}`);
5930
+ console.log(` Traffic sync: ${trafficSync2}`);
5931
+ }
5790
5932
  async function trafficConnectWordpress(project, opts) {
5791
5933
  if (!opts.url) {
5792
5934
  throw new CliError({
@@ -5814,9 +5956,9 @@ async function trafficConnectWordpress(project, opts) {
5814
5956
  }
5815
5957
  let applicationPassword = opts.appPassword?.trim() ?? "";
5816
5958
  if (!applicationPassword && opts.appPasswordFile) {
5817
- const fs18 = await import("fs");
5959
+ const fs19 = await import("fs");
5818
5960
  try {
5819
- applicationPassword = fs18.readFileSync(opts.appPasswordFile, "utf-8").trim();
5961
+ applicationPassword = fs19.readFileSync(opts.appPasswordFile, "utf-8").trim();
5820
5962
  } catch (e) {
5821
5963
  const msg = e instanceof Error ? e.message : String(e);
5822
5964
  throw new CliError({
@@ -5872,10 +6014,10 @@ async function trafficConnectCloudRun(project, opts) {
5872
6014
  details: { project }
5873
6015
  });
5874
6016
  }
5875
- const fs18 = await import("fs");
6017
+ const fs19 = await import("fs");
5876
6018
  let keyJson;
5877
6019
  try {
5878
- keyJson = fs18.readFileSync(opts.serviceAccountKey, "utf-8");
6020
+ keyJson = fs19.readFileSync(opts.serviceAccountKey, "utf-8");
5879
6021
  JSON.parse(keyJson);
5880
6022
  } catch (e) {
5881
6023
  const msg = e instanceof Error ? e.message : String(e);
@@ -5935,9 +6077,9 @@ async function trafficConnectVercel(project, opts) {
5935
6077
  }
5936
6078
  let token = opts.token?.trim() ?? "";
5937
6079
  if (!token && opts.tokenFile) {
5938
- const fs18 = await import("fs");
6080
+ const fs19 = await import("fs");
5939
6081
  try {
5940
- token = fs18.readFileSync(opts.tokenFile, "utf-8").trim();
6082
+ token = fs19.readFileSync(opts.tokenFile, "utf-8").trim();
5941
6083
  } catch (e) {
5942
6084
  const msg = e instanceof Error ? e.message : String(e);
5943
6085
  throw new CliError({
@@ -6288,11 +6430,16 @@ async function trafficEvents(project, opts) {
6288
6430
  var TRAFFIC_CLI_COMMANDS = [
6289
6431
  {
6290
6432
  path: ["traffic", "connect", "cloudflare"],
6291
- usage: "canonry traffic connect cloudflare <project> [--display-name <name>] [--zone-id <id>] [--account-id <id>] [--output-dir <dir>] [--deploy --confirm-route --confirm-fail-open] [--format json]",
6433
+ usage: "canonry traffic connect cloudflare <project> [--delivery-mode direct-push|queue-pull] [--display-name <name>] [--zone-id <id>] [--account-id <id>] [--queue-id <id> --queue-name <name> --api-token-file <path> --retention-seconds 345600] [--output-dir <dir>] [--deploy --confirm-route --confirm-fail-open] [--format json]",
6292
6434
  options: {
6293
6435
  "display-name": stringOption(),
6294
6436
  "zone-id": stringOption(),
6295
6437
  "account-id": stringOption(),
6438
+ "delivery-mode": stringOption(),
6439
+ "queue-id": stringOption(),
6440
+ "queue-name": stringOption(),
6441
+ "api-token-file": stringOption(),
6442
+ "retention-seconds": stringOption(),
6296
6443
  "output-dir": stringOption(),
6297
6444
  deploy: { type: "boolean" },
6298
6445
  "confirm-route": { type: "boolean" },
@@ -6302,12 +6449,26 @@ var TRAFFIC_CLI_COMMANDS = [
6302
6449
  const project = requireProject(
6303
6450
  input,
6304
6451
  "traffic.connect.cloudflare",
6305
- "canonry traffic connect cloudflare <project> [--zone-id <id>] [--output-dir <dir>] [--deploy --confirm-route --confirm-fail-open]"
6452
+ "canonry traffic connect cloudflare <project> [--delivery-mode direct-push|queue-pull] [--zone-id <id>] [--output-dir <dir>] [--deploy --confirm-route --confirm-fail-open]"
6306
6453
  );
6454
+ const deliveryMode = getString(input.values, "delivery-mode") ?? "direct-push";
6455
+ if (deliveryMode !== "direct-push" && deliveryMode !== "queue-pull") {
6456
+ throw new Error("--delivery-mode must be direct-push or queue-pull");
6457
+ }
6458
+ const retentionSeconds = parseIntegerOption(input, "retention-seconds", {
6459
+ command: "traffic.connect.cloudflare",
6460
+ usage: "canonry traffic connect cloudflare <project> --delivery-mode queue-pull --retention-seconds 345600",
6461
+ message: "--retention-seconds must be an integer"
6462
+ });
6307
6463
  await trafficConnectCloudflare(project, {
6464
+ deliveryMode,
6308
6465
  displayName: getString(input.values, "display-name"),
6309
6466
  zoneId: getString(input.values, "zone-id"),
6310
6467
  accountId: getString(input.values, "account-id"),
6468
+ queueId: getString(input.values, "queue-id"),
6469
+ queueName: getString(input.values, "queue-name"),
6470
+ apiTokenFile: getString(input.values, "api-token-file"),
6471
+ retentionSeconds: retentionSeconds ?? DEFAULT_CLOUDFLARE_QUEUE_RETENTION_SECONDS,
6311
6472
  outputDirectory: getString(input.values, "output-dir"),
6312
6473
  deploy: getBoolean(input.values, "deploy"),
6313
6474
  confirmRoute: getBoolean(input.values, "confirm-route"),
@@ -6419,6 +6580,23 @@ var TRAFFIC_CLI_COMMANDS = [
6419
6580
  });
6420
6581
  }
6421
6582
  },
6583
+ {
6584
+ path: ["traffic", "activate"],
6585
+ usage: "canonry traffic activate <project> --source <id> [--format json]",
6586
+ options: {
6587
+ source: stringOption()
6588
+ },
6589
+ run: async (input) => {
6590
+ const project = requireProject(
6591
+ input,
6592
+ "traffic.activate",
6593
+ "canonry traffic activate <project> --source <id>"
6594
+ );
6595
+ const sourceId = getString(input.values, "source");
6596
+ if (!sourceId) throw new Error("--source <id> is required");
6597
+ await trafficActivate(project, { sourceId, format: input.format });
6598
+ }
6599
+ },
6422
6600
  {
6423
6601
  path: ["traffic", "sync"],
6424
6602
  usage: "canonry traffic sync <project> --source <id> [--since-minutes 43200] [--format json]",
@@ -6568,7 +6746,7 @@ var TRAFFIC_CLI_COMMANDS = [
6568
6746
  unknownSubcommand(input.positionals[0], {
6569
6747
  command: "traffic",
6570
6748
  usage: "canonry traffic <subcommand> <project> [args]",
6571
- available: ["connect", "sync", "backfill", "status", "sources", "events"]
6749
+ available: ["connect", "activate", "sync", "backfill", "status", "sources", "events"]
6572
6750
  });
6573
6751
  }
6574
6752
  }
@@ -8310,7 +8488,7 @@ var USERS_CLI_COMMANDS = [
8310
8488
  ];
8311
8489
 
8312
8490
  // src/commands/keyword.ts
8313
- import fs5 from "fs";
8491
+ import fs6 from "fs";
8314
8492
  function getClient14() {
8315
8493
  return createApiClient();
8316
8494
  }
@@ -8378,7 +8556,7 @@ async function listKeywords(project, format) {
8378
8556
  }
8379
8557
  }
8380
8558
  async function importKeywords(project, filePath, format) {
8381
- if (!fs5.existsSync(filePath)) {
8559
+ if (!fs6.existsSync(filePath)) {
8382
8560
  throw new CliError({
8383
8561
  code: "KEYWORD_IMPORT_FILE_NOT_FOUND",
8384
8562
  message: `File not found: ${filePath}`,
@@ -8389,7 +8567,7 @@ async function importKeywords(project, filePath, format) {
8389
8567
  }
8390
8568
  });
8391
8569
  }
8392
- const content = fs5.readFileSync(filePath, "utf-8");
8570
+ const content = fs6.readFileSync(filePath, "utf-8");
8393
8571
  const keywords = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8394
8572
  if (keywords.length === 0) {
8395
8573
  if (isMachineFormat(format)) {
@@ -8590,7 +8768,7 @@ var KEYWORD_CLI_COMMANDS = [
8590
8768
  ];
8591
8769
 
8592
8770
  // src/commands/query.ts
8593
- import fs6 from "fs";
8771
+ import fs7 from "fs";
8594
8772
  function getClient15() {
8595
8773
  return createApiClient();
8596
8774
  }
@@ -8681,7 +8859,7 @@ async function listQueries(project, format) {
8681
8859
  }
8682
8860
  }
8683
8861
  async function importQueries(project, filePath, format) {
8684
- if (!fs6.existsSync(filePath)) {
8862
+ if (!fs7.existsSync(filePath)) {
8685
8863
  throw new CliError({
8686
8864
  code: "QUERY_IMPORT_FILE_NOT_FOUND",
8687
8865
  message: `File not found: ${filePath}`,
@@ -8692,7 +8870,7 @@ async function importQueries(project, filePath, format) {
8692
8870
  }
8693
8871
  });
8694
8872
  }
8695
- const content = fs6.readFileSync(filePath, "utf-8");
8873
+ const content = fs7.readFileSync(filePath, "utf-8");
8696
8874
  const queries2 = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8697
8875
  if (queries2.length === 0) {
8698
8876
  if (isMachineFormat(format)) {
@@ -8894,7 +9072,7 @@ var QUERY_CLI_COMMANDS = [
8894
9072
  ];
8895
9073
 
8896
9074
  // src/commands/mcp.ts
8897
- import fs7 from "fs";
9075
+ import fs8 from "fs";
8898
9076
  import path4 from "path";
8899
9077
  import { createRequire } from "module";
8900
9078
 
@@ -9010,8 +9188,8 @@ function renderClientSnippet(client, serverName, entry) {
9010
9188
  return renderJsonSnippet(serverName, entry, client.format);
9011
9189
  }
9012
9190
  function readJsonConfig(configPath) {
9013
- if (!fs7.existsSync(configPath)) return {};
9014
- const raw = fs7.readFileSync(configPath, "utf-8").trim();
9191
+ if (!fs8.existsSync(configPath)) return {};
9192
+ const raw = fs8.readFileSync(configPath, "utf-8").trim();
9015
9193
  if (!raw) return {};
9016
9194
  try {
9017
9195
  const parsed = JSON.parse(raw);
@@ -9029,14 +9207,14 @@ function readJsonConfig(configPath) {
9029
9207
  }
9030
9208
  }
9031
9209
  function writeJsonConfig(configPath, value) {
9032
- fs7.mkdirSync(path4.dirname(configPath), { recursive: true });
9033
- fs7.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
9210
+ fs8.mkdirSync(path4.dirname(configPath), { recursive: true });
9211
+ fs8.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
9034
9212
  `, "utf-8");
9035
9213
  }
9036
9214
  function backupConfigIfPresent(configPath) {
9037
- if (!fs7.existsSync(configPath)) return void 0;
9215
+ if (!fs8.existsSync(configPath)) return void 0;
9038
9216
  const backupPath = `${configPath}.canonry.bak`;
9039
- fs7.copyFileSync(configPath, backupPath);
9217
+ fs8.copyFileSync(configPath, backupPath);
9040
9218
  return backupPath;
9041
9219
  }
9042
9220
  function findClientOrThrow(id) {
@@ -9392,13 +9570,13 @@ var NOTIFY_CLI_COMMANDS = [
9392
9570
  ];
9393
9571
 
9394
9572
  // src/commands/apply.ts
9395
- import fs8 from "fs";
9573
+ import fs9 from "fs";
9396
9574
  import { parseAllDocuments } from "yaml";
9397
9575
  async function applyConfigFile(filePath) {
9398
- if (!fs8.existsSync(filePath)) {
9576
+ if (!fs9.existsSync(filePath)) {
9399
9577
  throw new Error(`File not found: ${filePath}`);
9400
9578
  }
9401
- const content = fs8.readFileSync(filePath, "utf-8");
9579
+ const content = fs9.readFileSync(filePath, "utf-8");
9402
9580
  const docs = parseAllDocuments(content);
9403
9581
  const client = createApiClient();
9404
9582
  const errors = [];
@@ -9801,7 +9979,7 @@ async function loadLatestRunForExport(client, project) {
9801
9979
  }
9802
9980
 
9803
9981
  // src/commands/results-export.ts
9804
- import fs9 from "fs";
9982
+ import fs10 from "fs";
9805
9983
  import path5 from "path";
9806
9984
  async function exportResults(project, opts) {
9807
9985
  const { output, ...request } = opts;
@@ -9811,8 +9989,8 @@ async function exportResults(project, opts) {
9811
9989
  return;
9812
9990
  }
9813
9991
  const target = output ? path5.resolve(output) : path5.resolve(process.cwd(), path5.basename(artifact.filename));
9814
- fs9.mkdirSync(path5.dirname(target), { recursive: true });
9815
- fs9.writeFileSync(target, artifact.content, "utf8");
9992
+ fs10.mkdirSync(path5.dirname(target), { recursive: true });
9993
+ fs10.writeFileSync(target, artifact.content, "utf8");
9816
9994
  console.log(`Results export written to ${target}`);
9817
9995
  }
9818
9996
 
@@ -10528,7 +10706,7 @@ function parseProviderModelAssignments(assignments) {
10528
10706
  }
10529
10707
 
10530
10708
  // src/commands/report.ts
10531
- import fs10 from "fs";
10709
+ import fs11 from "fs";
10532
10710
  import path6 from "path";
10533
10711
  function defaultOutputPath(project, audience) {
10534
10712
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -10545,10 +10723,10 @@ async function runReportCommand(project, opts = {}) {
10545
10723
  const html = renderReportHtml(report, { audience });
10546
10724
  const targetPath = opts.output ? path6.resolve(opts.output) : defaultOutputPath(project, audience);
10547
10725
  const dir = path6.dirname(targetPath);
10548
- if (!fs10.existsSync(dir)) {
10549
- fs10.mkdirSync(dir, { recursive: true });
10726
+ if (!fs11.existsSync(dir)) {
10727
+ fs11.mkdirSync(dir, { recursive: true });
10550
10728
  }
10551
- fs10.writeFileSync(targetPath, html, "utf-8");
10729
+ fs11.writeFileSync(targetPath, html, "utf-8");
10552
10730
  console.log(`Report written to ${targetPath}`);
10553
10731
  }
10554
10732
 
@@ -11523,11 +11701,11 @@ var SKILLS_CLI_COMMANDS = [
11523
11701
  ];
11524
11702
 
11525
11703
  // src/commands/snapshot.ts
11526
- import fs12 from "fs";
11704
+ import fs13 from "fs";
11527
11705
  import path8 from "path";
11528
11706
 
11529
11707
  // src/snapshot-pdf.ts
11530
- import fs11 from "fs";
11708
+ import fs12 from "fs";
11531
11709
  import path7 from "path";
11532
11710
  import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
11533
11711
  var PAGE_WIDTH = 612;
@@ -11741,8 +11919,8 @@ async function writeSnapshotPdf(report, outputPath) {
11741
11919
  renderQueries(pdf, report);
11742
11920
  const bytes = await doc.save();
11743
11921
  const resolvedPath = path7.resolve(outputPath);
11744
- fs11.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
11745
- fs11.writeFileSync(resolvedPath, bytes);
11922
+ fs12.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
11923
+ fs12.writeFileSync(resolvedPath, bytes);
11746
11924
  return resolvedPath;
11747
11925
  }
11748
11926
  function renderCover(pdf, report) {
@@ -11901,8 +12079,8 @@ PDF saved: ${savedPdfPath}`);
11901
12079
  }
11902
12080
  function writeSnapshotMarkdown(report, outputPath) {
11903
12081
  const resolvedPath = path8.resolve(outputPath);
11904
- fs12.mkdirSync(path8.dirname(resolvedPath), { recursive: true });
11905
- fs12.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
12082
+ fs13.mkdirSync(path8.dirname(resolvedPath), { recursive: true });
12083
+ fs13.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
11906
12084
  return resolvedPath;
11907
12085
  }
11908
12086
  function formatSnapshotMarkdown(report) {
@@ -13130,7 +13308,7 @@ async function bootstrapCommand(_opts) {
13130
13308
 
13131
13309
  // src/commands/daemon.ts
13132
13310
  import { spawn as spawn2 } from "child_process";
13133
- import fs13 from "fs";
13311
+ import fs14 from "fs";
13134
13312
  import path10 from "path";
13135
13313
  function getPidPath() {
13136
13314
  return path10.join(getConfigDir(), "canonry.pid");
@@ -13173,8 +13351,8 @@ function buildServeForwardArgs(opts) {
13173
13351
  async function startDaemon(opts) {
13174
13352
  const pidPath = getPidPath();
13175
13353
  const format = opts.format ?? "text";
13176
- if (fs13.existsSync(pidPath)) {
13177
- const existingPid = parseInt(fs13.readFileSync(pidPath, "utf-8").trim(), 10);
13354
+ if (fs14.existsSync(pidPath)) {
13355
+ const existingPid = parseInt(fs14.readFileSync(pidPath, "utf-8").trim(), 10);
13178
13356
  if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
13179
13357
  throw new CliError({
13180
13358
  code: "DAEMON_ALREADY_RUNNING",
@@ -13185,7 +13363,7 @@ async function startDaemon(opts) {
13185
13363
  }
13186
13364
  });
13187
13365
  }
13188
- fs13.unlinkSync(pidPath);
13366
+ fs14.unlinkSync(pidPath);
13189
13367
  }
13190
13368
  const cliPath = path10.resolve(new URL(import.meta.url).pathname);
13191
13369
  const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
@@ -13204,10 +13382,10 @@ async function startDaemon(opts) {
13204
13382
  });
13205
13383
  }
13206
13384
  const configDir = getConfigDir();
13207
- if (!fs13.existsSync(configDir)) {
13208
- fs13.mkdirSync(configDir, { recursive: true });
13385
+ if (!fs14.existsSync(configDir)) {
13386
+ fs14.mkdirSync(configDir, { recursive: true });
13209
13387
  }
13210
- fs13.writeFileSync(pidPath, String(child.pid), "utf-8");
13388
+ fs14.writeFileSync(pidPath, String(child.pid), "utf-8");
13211
13389
  const port = opts.port ?? "4100";
13212
13390
  const host = opts.host ?? "127.0.0.1";
13213
13391
  if (!isMachineFormat(format)) {
@@ -13216,7 +13394,7 @@ async function startDaemon(opts) {
13216
13394
  const ready = await waitForReady(host, port);
13217
13395
  if (!ready) {
13218
13396
  try {
13219
- fs13.unlinkSync(pidPath);
13397
+ fs14.unlinkSync(pidPath);
13220
13398
  } catch {
13221
13399
  }
13222
13400
  throw new CliError({
@@ -13248,7 +13426,7 @@ async function startDaemon(opts) {
13248
13426
  }
13249
13427
  function stopDaemon(format = "text") {
13250
13428
  const pidPath = getPidPath();
13251
- if (!fs13.existsSync(pidPath)) {
13429
+ if (!fs14.existsSync(pidPath)) {
13252
13430
  if (isMachineFormat(format)) {
13253
13431
  console.log(JSON.stringify({
13254
13432
  stopped: false,
@@ -13259,7 +13437,7 @@ function stopDaemon(format = "text") {
13259
13437
  console.log("Canonry is not running (no PID file found)");
13260
13438
  return;
13261
13439
  }
13262
- const pid = parseInt(fs13.readFileSync(pidPath, "utf-8").trim(), 10);
13440
+ const pid = parseInt(fs14.readFileSync(pidPath, "utf-8").trim(), 10);
13263
13441
  if (isNaN(pid)) {
13264
13442
  if (isMachineFormat(format)) {
13265
13443
  console.log(JSON.stringify({
@@ -13270,7 +13448,7 @@ function stopDaemon(format = "text") {
13270
13448
  } else {
13271
13449
  console.error("Invalid PID file. Removing it.");
13272
13450
  }
13273
- fs13.unlinkSync(pidPath);
13451
+ fs14.unlinkSync(pidPath);
13274
13452
  return;
13275
13453
  }
13276
13454
  if (!isProcessAlive(pid)) {
@@ -13284,12 +13462,12 @@ function stopDaemon(format = "text") {
13284
13462
  } else {
13285
13463
  console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
13286
13464
  }
13287
- fs13.unlinkSync(pidPath);
13465
+ fs14.unlinkSync(pidPath);
13288
13466
  return;
13289
13467
  }
13290
13468
  try {
13291
13469
  process.kill(pid, "SIGTERM");
13292
- fs13.unlinkSync(pidPath);
13470
+ fs14.unlinkSync(pidPath);
13293
13471
  if (isMachineFormat(format)) {
13294
13472
  console.log(JSON.stringify({
13295
13473
  stopped: true,
@@ -13313,7 +13491,7 @@ function stopDaemon(format = "text") {
13313
13491
 
13314
13492
  // src/commands/init.ts
13315
13493
  import crypto2 from "crypto";
13316
- import fs14 from "fs";
13494
+ import fs15 from "fs";
13317
13495
  import readline2 from "readline";
13318
13496
  import path11 from "path";
13319
13497
  var pendingServeHandoff = false;
@@ -13351,7 +13529,7 @@ var PROJECT_MARKERS = [".git", "canonry.yaml", "canonry.yml", "package.json"];
13351
13529
  function cwdLooksLikeProject(dir) {
13352
13530
  const home = process.env.HOME ?? "";
13353
13531
  if (home && path11.resolve(dir) === path11.resolve(home)) return false;
13354
- return PROJECT_MARKERS.some((marker) => fs14.existsSync(path11.join(dir, marker)));
13532
+ return PROJECT_MARKERS.some((marker) => fs15.existsSync(path11.join(dir, marker)));
13355
13533
  }
13356
13534
  var DEFAULT_AGENT_MODELS = {
13357
13535
  anthropic: "anthropic/claude-sonnet-4-6",
@@ -13382,8 +13560,8 @@ async function initCommand(opts) {
13382
13560
  return void 0;
13383
13561
  }
13384
13562
  const configDir = getConfigDir();
13385
- if (!fs14.existsSync(configDir)) {
13386
- fs14.mkdirSync(configDir, { recursive: true });
13563
+ if (!fs15.existsSync(configDir)) {
13564
+ fs15.mkdirSync(configDir, { recursive: true });
13387
13565
  }
13388
13566
  const bootstrapEnv = getBootstrapEnv(process.env, {
13389
13567
  GEMINI_API_KEY: opts?.geminiKey,
@@ -13684,7 +13862,7 @@ function encodeLegacySetupState(state) {
13684
13862
  }
13685
13863
 
13686
13864
  // src/agent-plugin.ts
13687
- import fs15 from "fs";
13865
+ import fs16 from "fs";
13688
13866
  import path12 from "path";
13689
13867
  var CANONRY_PLUGIN_ID = "canonry@canonry";
13690
13868
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -13729,7 +13907,7 @@ function isCanonryPluginId(value) {
13729
13907
  }
13730
13908
  function readJson(filePath) {
13731
13909
  try {
13732
- return JSON.parse(fs15.readFileSync(filePath, "utf8"));
13910
+ return JSON.parse(fs16.readFileSync(filePath, "utf8"));
13733
13911
  } catch {
13734
13912
  return null;
13735
13913
  }
@@ -13748,7 +13926,7 @@ function readCodexPluginConfigured(filePath) {
13748
13926
  try {
13749
13927
  let currentPlugin = null;
13750
13928
  let inPluginsTable = false;
13751
- for (const line of fs15.readFileSync(filePath, "utf8").split(/\r?\n/)) {
13929
+ for (const line of fs16.readFileSync(filePath, "utf8").split(/\r?\n/)) {
13752
13930
  const section = /^\s*\[plugins\.(?:"([^"]+)"|'([^']+)'|(\S+))\]\s*(?:#.*)?$/.exec(line);
13753
13931
  if (section) {
13754
13932
  const captures = section.slice(1, 4);
@@ -13781,7 +13959,7 @@ function readCodexPluginConfigured(filePath) {
13781
13959
  }
13782
13960
  }
13783
13961
  function verifiedPluginVersion(root, client) {
13784
- if (client === "codex" && fs15.existsSync(path12.join(root, "plugin.json"))) {
13962
+ if (client === "codex" && fs16.existsSync(path12.join(root, "plugin.json"))) {
13785
13963
  const manifest2 = readJson(path12.join(root, "plugin.json"));
13786
13964
  if (manifest2 && typeof manifest2 === "object") {
13787
13965
  const plugin2 = manifest2;
@@ -13795,7 +13973,7 @@ function verifiedPluginVersion(root, client) {
13795
13973
  if (portableMcp.$schema !== AGENT_PLUGINS_MCP_SCHEMA || portableMcp.mcpServers?.canonry?.type !== "stdio" || portableMcp.mcpServers.canonry.command !== "canonry-mcp") {
13796
13974
  return null;
13797
13975
  }
13798
- return REQUIRED_SKILLS.every((name) => fs15.existsSync(path12.join(root, "skills", name, "SKILL.md"))) ? plugin2.version : null;
13976
+ return REQUIRED_SKILLS.every((name) => fs16.existsSync(path12.join(root, "skills", name, "SKILL.md"))) ? plugin2.version : null;
13799
13977
  }
13800
13978
  if (typeof plugin2.$schema === "string" && plugin2.$schema.startsWith("https://agent-plugins.org/schemas/")) {
13801
13979
  return null;
@@ -13813,7 +13991,7 @@ function verifiedPluginVersion(root, client) {
13813
13991
  if (!mcp || typeof mcp !== "object") return null;
13814
13992
  const canonryServer = mcp.mcpServers?.canonry;
13815
13993
  if (canonryServer?.command !== "canonry-mcp") return null;
13816
- return REQUIRED_SKILLS.every((name) => fs15.existsSync(path12.join(root, "skills", name, "SKILL.md"))) ? plugin.version : null;
13994
+ return REQUIRED_SKILLS.every((name) => fs16.existsSync(path12.join(root, "skills", name, "SKILL.md"))) ? plugin.version : null;
13817
13995
  }
13818
13996
  function readClaudeUserInstallPaths(claudeConfigDir) {
13819
13997
  const parsed = readJson(path12.join(claudeConfigDir, "plugins", "installed_plugins.json"));
@@ -13830,7 +14008,7 @@ function readClaudeUserInstallPaths(claudeConfigDir) {
13830
14008
  }
13831
14009
  function listDirectories(dir) {
13832
14010
  try {
13833
- return fs15.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path12.join(dir, entry.name));
14011
+ return fs16.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path12.join(dir, entry.name));
13834
14012
  } catch {
13835
14013
  return [];
13836
14014
  }
@@ -14354,6 +14532,29 @@ async function technicalAeoTrend(project, opts) {
14354
14532
  }
14355
14533
  console.log(lines.join("\n"));
14356
14534
  }
14535
+ async function technicalAeoProgress(project, opts) {
14536
+ const progress = await getClient27().getTechnicalAeoProgress(project, opts.runId);
14537
+ if (isMachineFormat(opts.format)) {
14538
+ console.log(JSON.stringify(progress, null, 2));
14539
+ return;
14540
+ }
14541
+ const lines = [
14542
+ `Site Health progress: ${progress.phase} (${progress.status})`,
14543
+ `Run: ${progress.runId}`
14544
+ ];
14545
+ if (progress.attempt) {
14546
+ lines.push(
14547
+ `Pages: ${progress.attempt.pagesDiscovered} found \xB7 ${progress.attempt.pagesFetched} checked \xB7 ${progress.attempt.pagesErrored} failed`,
14548
+ `Eligible: ${progress.attempt.pagesEligible} \xB7 Links found: ${progress.attempt.edgesDiscovered}`,
14549
+ `Updated: ${progress.attempt.lastUpdatedAt}`
14550
+ );
14551
+ } else {
14552
+ lines.push("Pages: waiting for the crawl to start");
14553
+ }
14554
+ lines.push(`Layout: ${progress.layout.state}`);
14555
+ if (progress.error) lines.push(`Error: ${progress.error}`);
14556
+ console.log(lines.join("\n"));
14557
+ }
14357
14558
  async function technicalAeoCrawl(project, opts) {
14358
14559
  const crawl = await getClient27().getTechnicalAeoCrawl(project, { runId: opts.runId });
14359
14560
  if (isMachineFormat(opts.format)) {
@@ -14848,6 +15049,21 @@ var TECHNICAL_AEO_CLI_COMMANDS_BASE = [
14848
15049
  await technicalAeoCrawl(project, { runId: getString(input.values, "run-id"), format: input.format });
14849
15050
  }
14850
15051
  },
15052
+ {
15053
+ path: ["technical-aeo", "progress"],
15054
+ usage: "canonry technical-aeo progress <project> --run-id <id> [--format json]",
15055
+ options: { "run-id": stringOption() },
15056
+ run: async (input) => {
15057
+ const usage = "canonry technical-aeo progress <project> --run-id <id> [--format json]";
15058
+ const project = requireProject(input, "technical-aeo.progress", usage);
15059
+ const runId = requireStringOption(input, "run-id", {
15060
+ command: "technical-aeo.progress",
15061
+ usage,
15062
+ message: "--run-id is required"
15063
+ });
15064
+ await technicalAeoProgress(project, { runId, format: input.format });
15065
+ }
15066
+ },
14851
15067
  {
14852
15068
  path: ["technical-aeo", "subgraph"],
14853
15069
  usage: "canonry technical-aeo subgraph <project> [--run-id <id>] [--node-key <key>|--url <url>] [--hops <n>] [--max-nodes <n>] [--max-edges <n>] [--format json]",
@@ -15445,7 +15661,7 @@ var VISIBILITY_STATS_CLI_COMMANDS = [
15445
15661
  ];
15446
15662
 
15447
15663
  // src/cli-commands/wordpress.ts
15448
- import fs16 from "fs";
15664
+ import fs17 from "fs";
15449
15665
 
15450
15666
  // src/commands/wordpress.ts
15451
15667
  function getClient28() {
@@ -15684,12 +15900,12 @@ async function wordpressSetMeta(project, body) {
15684
15900
  printPageDetail(result);
15685
15901
  }
15686
15902
  async function wordpressBulkSetMeta(project, opts) {
15687
- const fs18 = await import("fs/promises");
15903
+ const fs19 = await import("fs/promises");
15688
15904
  const path13 = await import("path");
15689
15905
  const filePath = path13.resolve(opts.from);
15690
15906
  let raw;
15691
15907
  try {
15692
- raw = await fs18.readFile(filePath, "utf8");
15908
+ raw = await fs19.readFile(filePath, "utf8");
15693
15909
  } catch {
15694
15910
  throw new CliError({
15695
15911
  code: "FILE_READ_ERROR",
@@ -15786,13 +16002,13 @@ async function wordpressSetSchema(project, body) {
15786
16002
  printManualAssist(`Schema update for "${body.slug}"`, result);
15787
16003
  }
15788
16004
  async function wordpressSchemaDeploy(project, opts) {
15789
- const fs18 = await import("fs/promises");
16005
+ const fs19 = await import("fs/promises");
15790
16006
  const path13 = await import("path");
15791
16007
  const yaml = await loadYamlModule();
15792
16008
  const filePath = path13.resolve(opts.profile);
15793
16009
  let raw;
15794
16010
  try {
15795
- raw = await fs18.readFile(filePath, "utf8");
16011
+ raw = await fs19.readFile(filePath, "utf8");
15796
16012
  } catch {
15797
16013
  throw new CliError({
15798
16014
  code: "FILE_READ_ERROR",
@@ -15897,13 +16113,13 @@ async function wordpressOnboard(project, opts) {
15897
16113
  }
15898
16114
  let profileData;
15899
16115
  if (opts.profile) {
15900
- const fs18 = await import("fs/promises");
16116
+ const fs19 = await import("fs/promises");
15901
16117
  const path13 = await import("path");
15902
16118
  const yaml = await loadYamlModule();
15903
16119
  const filePath = path13.resolve(opts.profile);
15904
16120
  let raw;
15905
16121
  try {
15906
- raw = await fs18.readFile(filePath, "utf8");
16122
+ raw = await fs19.readFile(filePath, "utf8");
15907
16123
  } catch {
15908
16124
  throw new CliError({
15909
16125
  code: "FILE_READ_ERROR",
@@ -16052,7 +16268,7 @@ function resolveContent(input, command, usage, options) {
16052
16268
  }
16053
16269
  if (contentFile) {
16054
16270
  try {
16055
- return fs16.readFileSync(contentFile, "utf-8");
16271
+ return fs17.readFileSync(contentFile, "utf-8");
16056
16272
  } catch (error) {
16057
16273
  const message = error instanceof Error ? error.message : String(error);
16058
16274
  throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {
@@ -17019,16 +17235,16 @@ Usage: ${usage}`, {
17019
17235
 
17020
17236
  // src/commands/measurement-plan.ts
17021
17237
  import crypto3 from "crypto";
17022
- import fs17 from "fs";
17238
+ import fs18 from "fs";
17023
17239
  import { parse } from "yaml";
17024
17240
  import { z as z2 } from "zod";
17025
17241
  function readPlan(source) {
17026
- const content = source === "-" ? fs17.readFileSync(0, "utf8") : fs17.readFileSync(source, "utf8");
17242
+ const content = source === "-" ? fs18.readFileSync(0, "utf8") : fs18.readFileSync(source, "utf8");
17027
17243
  const parsed = source.endsWith(".json") ? JSON.parse(content) : parse(content);
17028
17244
  return measurementPlanInputSchema.parse(parsed);
17029
17245
  }
17030
17246
  function readDiscoveryRule(source) {
17031
- const content = source === "-" ? fs17.readFileSync(0, "utf8") : fs17.readFileSync(source, "utf8");
17247
+ const content = source === "-" ? fs18.readFileSync(0, "utf8") : fs18.readFileSync(source, "utf8");
17032
17248
  const parsed = source.endsWith(".json") ? JSON.parse(content) : parse(content);
17033
17249
  return measurementDiscoveryRuleSchema.parse(parsed);
17034
17250
  }
@@ -17077,7 +17293,7 @@ var advancedDeactivateInputSchema = z2.object({
17077
17293
  }).strict();
17078
17294
  function readAdvancedMeasurementInput(source) {
17079
17295
  if (source === void 0) return {};
17080
- const content = source === "-" ? fs17.readFileSync(0, "utf8") : fs17.readFileSync(source, "utf8");
17296
+ const content = source === "-" ? fs18.readFileSync(0, "utf8") : fs18.readFileSync(source, "utf8");
17081
17297
  return JSON.parse(content);
17082
17298
  }
17083
17299
  var advancedMeasurementCollectionKeys = {
@@ -17259,7 +17475,7 @@ async function replaceMeasurementPlanAssignments(project, opts) {
17259
17475
  console.log(JSON.stringify({ preview, result }, null, 2));
17260
17476
  }
17261
17477
  function readGroupMembershipCsv(source) {
17262
- return source === "-" ? fs17.readFileSync(0, "utf8") : fs17.readFileSync(source, "utf8");
17478
+ return source === "-" ? fs18.readFileSync(0, "utf8") : fs18.readFileSync(source, "utf8");
17263
17479
  }
17264
17480
  async function previewMeasurementPlanGroups(project, source) {
17265
17481
  const preview = await createApiClient().previewMeasurementDraftGroupMembership(project, {