@canonry/canonry 4.159.0 → 4.161.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 (30) hide show
  1. package/README.md +1 -1
  2. package/assets/agent-workspace/skills/aero/SKILL.md +16 -1
  3. package/assets/agent-workspace/skills/canonry/SKILL.md +17 -5
  4. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +66 -0
  5. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +231 -62
  6. package/assets/assets/{AuditHistoryPanel-BiloGcQA.js → AuditHistoryPanel-C_1twF00.js} +1 -1
  7. package/assets/assets/{BacklinksPage-CPM1O2bC.js → BacklinksPage-CfAuMCaN.js} +1 -1
  8. package/assets/assets/{HistoryPage-1YOjtdJy.js → HistoryPage-DrRxOL3j.js} +1 -1
  9. package/assets/assets/{MeasurementPropertyPage-CVDTY_gC.js → MeasurementPropertyPage-BFtxx5Jw.js} +1 -1
  10. package/assets/assets/{ProjectPage-DnGXMx1n.js → ProjectPage-Djvhx4HD.js} +9 -9
  11. package/assets/assets/{RunRow-fIaadH4h.js → RunRow-DVZXibR2.js} +1 -1
  12. package/assets/assets/{RunsPage-DfifdM5z.js → RunsPage-CAUMy7By.js} +1 -1
  13. package/assets/assets/{SettingsPage-efA2EEmS.js → SettingsPage-CcSOmQAl.js} +1 -1
  14. package/assets/assets/{SiteHealthSection-BROtQGfu.js → SiteHealthSection-DYjEWd1I.js} +3 -3
  15. package/assets/assets/{TrafficPage-Wk43Bm_q.js → TrafficPage-BWM33jnJ.js} +1 -1
  16. package/assets/assets/{TrafficSourceDetailPage-BhBvoLWu.js → TrafficSourceDetailPage-DvEZ_JGY.js} +1 -1
  17. package/assets/assets/{extract-error-message-YGzZFvWs.js → extract-error-message-CnKOJkYB.js} +1 -1
  18. package/assets/assets/{index-DmsIZQAm.js → index-CQGAqmDx.js} +22 -22
  19. package/assets/assets/{react-sigma_core.esm.min-DM9XptbM.js → react-sigma_core.esm.min-CHCAIMBW.js} +1 -1
  20. package/assets/index.html +1 -1
  21. package/dist/{chunk-ZL3VY435.js → chunk-EXDO7EPL.js} +104 -9
  22. package/dist/{chunk-ZEG43TY2.js → chunk-HCGTKQCG.js} +21 -1
  23. package/dist/{chunk-5AFWHKKI.js → chunk-J27J2DF6.js} +2095 -703
  24. package/dist/{chunk-BZPBVOAM.js → chunk-W6OWEPZG.js} +70 -30
  25. package/dist/cli.js +308 -124
  26. package/dist/index.d.ts +28 -14
  27. package/dist/index.js +4 -4
  28. package/dist/{intelligence-service-DHY5CYWX.js → intelligence-service-HKDIVTZB.js} +2 -2
  29. package/dist/mcp.js +2 -2
  30. package/package.json +8 -7
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-BZPBVOAM.js";
34
+ } from "./chunk-W6OWEPZG.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-ZEG43TY2.js";
54
+ } from "./chunk-HCGTKQCG.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-5AFWHKKI.js";
68
+ } from "./chunk-J27J2DF6.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-ZL3VY435.js";
132
+ } from "./chunk-EXDO7EPL.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
  }
@@ -6914,7 +7092,7 @@ async function googlePerformanceDaily(project, opts) {
6914
7092
  console.log(JSON.stringify(data, null, 2));
6915
7093
  return;
6916
7094
  } else if (opts.format === "jsonl") {
6917
- emitJsonl(data.daily.map((row) => ({ project, ...row })));
7095
+ emitJsonl(data.daily.map((row) => ({ project, window: data.window, ...row })));
6918
7096
  return;
6919
7097
  }
6920
7098
  if (data.daily.length === 0) {
@@ -6922,8 +7100,18 @@ async function googlePerformanceDaily(project, opts) {
6922
7100
  return;
6923
7101
  }
6924
7102
  const { clicks, impressions, ctr, days } = data.totals;
6925
- console.log(`GSC daily summary (${days} day${days === 1 ? "" : "s"}):
7103
+ const win = data.window;
7104
+ const range = win?.startDate && win.endDate ? ` ${win.startDate} to ${win.endDate}` : "";
7105
+ console.log(`GSC daily summary (${days} day${days === 1 ? "" : "s"}${range}):
6926
7106
  `);
7107
+ const endsAtFrontier = win?.endDate !== void 0 && win.endDate === win.latestDataDate;
7108
+ if (endsAtFrontier && (win.daysSinceLatestData ?? 0) > 0) {
7109
+ console.log(` Search Console data through ${win.endDate} (${win.daysSinceLatestData} day${win.daysSinceLatestData === 1 ? "" : "s"} ago).`);
7110
+ console.log();
7111
+ } else if (win?.latestDataDate && win.endDate !== win.latestDataDate) {
7112
+ console.log(` Showing a fixed range. Latest Search Console data is ${win.latestDataDate}.`);
7113
+ console.log();
7114
+ }
6927
7115
  console.log(` Clicks: ${clicks.toLocaleString()}`);
6928
7116
  console.log(` Impressions: ${impressions.toLocaleString()}`);
6929
7117
  console.log(` CTR: ${(ctr * 100).toFixed(2)}%`);
@@ -6988,11 +7176,7 @@ async function googlePerformance(project, opts) {
6988
7176
  if (opts.startDate) params.startDate = opts.startDate;
6989
7177
  if (opts.endDate) params.endDate = opts.endDate;
6990
7178
  } else if (opts.days) {
6991
- const end = /* @__PURE__ */ new Date();
6992
- const start = /* @__PURE__ */ new Date();
6993
- start.setDate(start.getDate() - opts.days);
6994
- params.startDate = start.toISOString().split("T")[0];
6995
- params.endDate = end.toISOString().split("T")[0];
7179
+ params.days = String(opts.days);
6996
7180
  }
6997
7181
  if (opts.keyword) params.query = opts.keyword;
6998
7182
  if (opts.page) params.page = opts.page;
@@ -8310,7 +8494,7 @@ var USERS_CLI_COMMANDS = [
8310
8494
  ];
8311
8495
 
8312
8496
  // src/commands/keyword.ts
8313
- import fs5 from "fs";
8497
+ import fs6 from "fs";
8314
8498
  function getClient14() {
8315
8499
  return createApiClient();
8316
8500
  }
@@ -8378,7 +8562,7 @@ async function listKeywords(project, format) {
8378
8562
  }
8379
8563
  }
8380
8564
  async function importKeywords(project, filePath, format) {
8381
- if (!fs5.existsSync(filePath)) {
8565
+ if (!fs6.existsSync(filePath)) {
8382
8566
  throw new CliError({
8383
8567
  code: "KEYWORD_IMPORT_FILE_NOT_FOUND",
8384
8568
  message: `File not found: ${filePath}`,
@@ -8389,7 +8573,7 @@ async function importKeywords(project, filePath, format) {
8389
8573
  }
8390
8574
  });
8391
8575
  }
8392
- const content = fs5.readFileSync(filePath, "utf-8");
8576
+ const content = fs6.readFileSync(filePath, "utf-8");
8393
8577
  const keywords = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8394
8578
  if (keywords.length === 0) {
8395
8579
  if (isMachineFormat(format)) {
@@ -8590,7 +8774,7 @@ var KEYWORD_CLI_COMMANDS = [
8590
8774
  ];
8591
8775
 
8592
8776
  // src/commands/query.ts
8593
- import fs6 from "fs";
8777
+ import fs7 from "fs";
8594
8778
  function getClient15() {
8595
8779
  return createApiClient();
8596
8780
  }
@@ -8681,7 +8865,7 @@ async function listQueries(project, format) {
8681
8865
  }
8682
8866
  }
8683
8867
  async function importQueries(project, filePath, format) {
8684
- if (!fs6.existsSync(filePath)) {
8868
+ if (!fs7.existsSync(filePath)) {
8685
8869
  throw new CliError({
8686
8870
  code: "QUERY_IMPORT_FILE_NOT_FOUND",
8687
8871
  message: `File not found: ${filePath}`,
@@ -8692,7 +8876,7 @@ async function importQueries(project, filePath, format) {
8692
8876
  }
8693
8877
  });
8694
8878
  }
8695
- const content = fs6.readFileSync(filePath, "utf-8");
8879
+ const content = fs7.readFileSync(filePath, "utf-8");
8696
8880
  const queries2 = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8697
8881
  if (queries2.length === 0) {
8698
8882
  if (isMachineFormat(format)) {
@@ -8894,7 +9078,7 @@ var QUERY_CLI_COMMANDS = [
8894
9078
  ];
8895
9079
 
8896
9080
  // src/commands/mcp.ts
8897
- import fs7 from "fs";
9081
+ import fs8 from "fs";
8898
9082
  import path4 from "path";
8899
9083
  import { createRequire } from "module";
8900
9084
 
@@ -9010,8 +9194,8 @@ function renderClientSnippet(client, serverName, entry) {
9010
9194
  return renderJsonSnippet(serverName, entry, client.format);
9011
9195
  }
9012
9196
  function readJsonConfig(configPath) {
9013
- if (!fs7.existsSync(configPath)) return {};
9014
- const raw = fs7.readFileSync(configPath, "utf-8").trim();
9197
+ if (!fs8.existsSync(configPath)) return {};
9198
+ const raw = fs8.readFileSync(configPath, "utf-8").trim();
9015
9199
  if (!raw) return {};
9016
9200
  try {
9017
9201
  const parsed = JSON.parse(raw);
@@ -9029,14 +9213,14 @@ function readJsonConfig(configPath) {
9029
9213
  }
9030
9214
  }
9031
9215
  function writeJsonConfig(configPath, value) {
9032
- fs7.mkdirSync(path4.dirname(configPath), { recursive: true });
9033
- fs7.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
9216
+ fs8.mkdirSync(path4.dirname(configPath), { recursive: true });
9217
+ fs8.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
9034
9218
  `, "utf-8");
9035
9219
  }
9036
9220
  function backupConfigIfPresent(configPath) {
9037
- if (!fs7.existsSync(configPath)) return void 0;
9221
+ if (!fs8.existsSync(configPath)) return void 0;
9038
9222
  const backupPath = `${configPath}.canonry.bak`;
9039
- fs7.copyFileSync(configPath, backupPath);
9223
+ fs8.copyFileSync(configPath, backupPath);
9040
9224
  return backupPath;
9041
9225
  }
9042
9226
  function findClientOrThrow(id) {
@@ -9392,13 +9576,13 @@ var NOTIFY_CLI_COMMANDS = [
9392
9576
  ];
9393
9577
 
9394
9578
  // src/commands/apply.ts
9395
- import fs8 from "fs";
9579
+ import fs9 from "fs";
9396
9580
  import { parseAllDocuments } from "yaml";
9397
9581
  async function applyConfigFile(filePath) {
9398
- if (!fs8.existsSync(filePath)) {
9582
+ if (!fs9.existsSync(filePath)) {
9399
9583
  throw new Error(`File not found: ${filePath}`);
9400
9584
  }
9401
- const content = fs8.readFileSync(filePath, "utf-8");
9585
+ const content = fs9.readFileSync(filePath, "utf-8");
9402
9586
  const docs = parseAllDocuments(content);
9403
9587
  const client = createApiClient();
9404
9588
  const errors = [];
@@ -9801,7 +9985,7 @@ async function loadLatestRunForExport(client, project) {
9801
9985
  }
9802
9986
 
9803
9987
  // src/commands/results-export.ts
9804
- import fs9 from "fs";
9988
+ import fs10 from "fs";
9805
9989
  import path5 from "path";
9806
9990
  async function exportResults(project, opts) {
9807
9991
  const { output, ...request } = opts;
@@ -9811,8 +9995,8 @@ async function exportResults(project, opts) {
9811
9995
  return;
9812
9996
  }
9813
9997
  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");
9998
+ fs10.mkdirSync(path5.dirname(target), { recursive: true });
9999
+ fs10.writeFileSync(target, artifact.content, "utf8");
9816
10000
  console.log(`Results export written to ${target}`);
9817
10001
  }
9818
10002
 
@@ -10528,7 +10712,7 @@ function parseProviderModelAssignments(assignments) {
10528
10712
  }
10529
10713
 
10530
10714
  // src/commands/report.ts
10531
- import fs10 from "fs";
10715
+ import fs11 from "fs";
10532
10716
  import path6 from "path";
10533
10717
  function defaultOutputPath(project, audience) {
10534
10718
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -10545,10 +10729,10 @@ async function runReportCommand(project, opts = {}) {
10545
10729
  const html = renderReportHtml(report, { audience });
10546
10730
  const targetPath = opts.output ? path6.resolve(opts.output) : defaultOutputPath(project, audience);
10547
10731
  const dir = path6.dirname(targetPath);
10548
- if (!fs10.existsSync(dir)) {
10549
- fs10.mkdirSync(dir, { recursive: true });
10732
+ if (!fs11.existsSync(dir)) {
10733
+ fs11.mkdirSync(dir, { recursive: true });
10550
10734
  }
10551
- fs10.writeFileSync(targetPath, html, "utf-8");
10735
+ fs11.writeFileSync(targetPath, html, "utf-8");
10552
10736
  console.log(`Report written to ${targetPath}`);
10553
10737
  }
10554
10738
 
@@ -11523,11 +11707,11 @@ var SKILLS_CLI_COMMANDS = [
11523
11707
  ];
11524
11708
 
11525
11709
  // src/commands/snapshot.ts
11526
- import fs12 from "fs";
11710
+ import fs13 from "fs";
11527
11711
  import path8 from "path";
11528
11712
 
11529
11713
  // src/snapshot-pdf.ts
11530
- import fs11 from "fs";
11714
+ import fs12 from "fs";
11531
11715
  import path7 from "path";
11532
11716
  import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
11533
11717
  var PAGE_WIDTH = 612;
@@ -11741,8 +11925,8 @@ async function writeSnapshotPdf(report, outputPath) {
11741
11925
  renderQueries(pdf, report);
11742
11926
  const bytes = await doc.save();
11743
11927
  const resolvedPath = path7.resolve(outputPath);
11744
- fs11.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
11745
- fs11.writeFileSync(resolvedPath, bytes);
11928
+ fs12.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
11929
+ fs12.writeFileSync(resolvedPath, bytes);
11746
11930
  return resolvedPath;
11747
11931
  }
11748
11932
  function renderCover(pdf, report) {
@@ -11901,8 +12085,8 @@ PDF saved: ${savedPdfPath}`);
11901
12085
  }
11902
12086
  function writeSnapshotMarkdown(report, outputPath) {
11903
12087
  const resolvedPath = path8.resolve(outputPath);
11904
- fs12.mkdirSync(path8.dirname(resolvedPath), { recursive: true });
11905
- fs12.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
12088
+ fs13.mkdirSync(path8.dirname(resolvedPath), { recursive: true });
12089
+ fs13.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
11906
12090
  return resolvedPath;
11907
12091
  }
11908
12092
  function formatSnapshotMarkdown(report) {
@@ -13130,7 +13314,7 @@ async function bootstrapCommand(_opts) {
13130
13314
 
13131
13315
  // src/commands/daemon.ts
13132
13316
  import { spawn as spawn2 } from "child_process";
13133
- import fs13 from "fs";
13317
+ import fs14 from "fs";
13134
13318
  import path10 from "path";
13135
13319
  function getPidPath() {
13136
13320
  return path10.join(getConfigDir(), "canonry.pid");
@@ -13173,8 +13357,8 @@ function buildServeForwardArgs(opts) {
13173
13357
  async function startDaemon(opts) {
13174
13358
  const pidPath = getPidPath();
13175
13359
  const format = opts.format ?? "text";
13176
- if (fs13.existsSync(pidPath)) {
13177
- const existingPid = parseInt(fs13.readFileSync(pidPath, "utf-8").trim(), 10);
13360
+ if (fs14.existsSync(pidPath)) {
13361
+ const existingPid = parseInt(fs14.readFileSync(pidPath, "utf-8").trim(), 10);
13178
13362
  if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
13179
13363
  throw new CliError({
13180
13364
  code: "DAEMON_ALREADY_RUNNING",
@@ -13185,7 +13369,7 @@ async function startDaemon(opts) {
13185
13369
  }
13186
13370
  });
13187
13371
  }
13188
- fs13.unlinkSync(pidPath);
13372
+ fs14.unlinkSync(pidPath);
13189
13373
  }
13190
13374
  const cliPath = path10.resolve(new URL(import.meta.url).pathname);
13191
13375
  const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
@@ -13204,10 +13388,10 @@ async function startDaemon(opts) {
13204
13388
  });
13205
13389
  }
13206
13390
  const configDir = getConfigDir();
13207
- if (!fs13.existsSync(configDir)) {
13208
- fs13.mkdirSync(configDir, { recursive: true });
13391
+ if (!fs14.existsSync(configDir)) {
13392
+ fs14.mkdirSync(configDir, { recursive: true });
13209
13393
  }
13210
- fs13.writeFileSync(pidPath, String(child.pid), "utf-8");
13394
+ fs14.writeFileSync(pidPath, String(child.pid), "utf-8");
13211
13395
  const port = opts.port ?? "4100";
13212
13396
  const host = opts.host ?? "127.0.0.1";
13213
13397
  if (!isMachineFormat(format)) {
@@ -13216,7 +13400,7 @@ async function startDaemon(opts) {
13216
13400
  const ready = await waitForReady(host, port);
13217
13401
  if (!ready) {
13218
13402
  try {
13219
- fs13.unlinkSync(pidPath);
13403
+ fs14.unlinkSync(pidPath);
13220
13404
  } catch {
13221
13405
  }
13222
13406
  throw new CliError({
@@ -13248,7 +13432,7 @@ async function startDaemon(opts) {
13248
13432
  }
13249
13433
  function stopDaemon(format = "text") {
13250
13434
  const pidPath = getPidPath();
13251
- if (!fs13.existsSync(pidPath)) {
13435
+ if (!fs14.existsSync(pidPath)) {
13252
13436
  if (isMachineFormat(format)) {
13253
13437
  console.log(JSON.stringify({
13254
13438
  stopped: false,
@@ -13259,7 +13443,7 @@ function stopDaemon(format = "text") {
13259
13443
  console.log("Canonry is not running (no PID file found)");
13260
13444
  return;
13261
13445
  }
13262
- const pid = parseInt(fs13.readFileSync(pidPath, "utf-8").trim(), 10);
13446
+ const pid = parseInt(fs14.readFileSync(pidPath, "utf-8").trim(), 10);
13263
13447
  if (isNaN(pid)) {
13264
13448
  if (isMachineFormat(format)) {
13265
13449
  console.log(JSON.stringify({
@@ -13270,7 +13454,7 @@ function stopDaemon(format = "text") {
13270
13454
  } else {
13271
13455
  console.error("Invalid PID file. Removing it.");
13272
13456
  }
13273
- fs13.unlinkSync(pidPath);
13457
+ fs14.unlinkSync(pidPath);
13274
13458
  return;
13275
13459
  }
13276
13460
  if (!isProcessAlive(pid)) {
@@ -13284,12 +13468,12 @@ function stopDaemon(format = "text") {
13284
13468
  } else {
13285
13469
  console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
13286
13470
  }
13287
- fs13.unlinkSync(pidPath);
13471
+ fs14.unlinkSync(pidPath);
13288
13472
  return;
13289
13473
  }
13290
13474
  try {
13291
13475
  process.kill(pid, "SIGTERM");
13292
- fs13.unlinkSync(pidPath);
13476
+ fs14.unlinkSync(pidPath);
13293
13477
  if (isMachineFormat(format)) {
13294
13478
  console.log(JSON.stringify({
13295
13479
  stopped: true,
@@ -13313,7 +13497,7 @@ function stopDaemon(format = "text") {
13313
13497
 
13314
13498
  // src/commands/init.ts
13315
13499
  import crypto2 from "crypto";
13316
- import fs14 from "fs";
13500
+ import fs15 from "fs";
13317
13501
  import readline2 from "readline";
13318
13502
  import path11 from "path";
13319
13503
  var pendingServeHandoff = false;
@@ -13351,7 +13535,7 @@ var PROJECT_MARKERS = [".git", "canonry.yaml", "canonry.yml", "package.json"];
13351
13535
  function cwdLooksLikeProject(dir) {
13352
13536
  const home = process.env.HOME ?? "";
13353
13537
  if (home && path11.resolve(dir) === path11.resolve(home)) return false;
13354
- return PROJECT_MARKERS.some((marker) => fs14.existsSync(path11.join(dir, marker)));
13538
+ return PROJECT_MARKERS.some((marker) => fs15.existsSync(path11.join(dir, marker)));
13355
13539
  }
13356
13540
  var DEFAULT_AGENT_MODELS = {
13357
13541
  anthropic: "anthropic/claude-sonnet-4-6",
@@ -13382,8 +13566,8 @@ async function initCommand(opts) {
13382
13566
  return void 0;
13383
13567
  }
13384
13568
  const configDir = getConfigDir();
13385
- if (!fs14.existsSync(configDir)) {
13386
- fs14.mkdirSync(configDir, { recursive: true });
13569
+ if (!fs15.existsSync(configDir)) {
13570
+ fs15.mkdirSync(configDir, { recursive: true });
13387
13571
  }
13388
13572
  const bootstrapEnv = getBootstrapEnv(process.env, {
13389
13573
  GEMINI_API_KEY: opts?.geminiKey,
@@ -13684,7 +13868,7 @@ function encodeLegacySetupState(state) {
13684
13868
  }
13685
13869
 
13686
13870
  // src/agent-plugin.ts
13687
- import fs15 from "fs";
13871
+ import fs16 from "fs";
13688
13872
  import path12 from "path";
13689
13873
  var CANONRY_PLUGIN_ID = "canonry@canonry";
13690
13874
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -13729,7 +13913,7 @@ function isCanonryPluginId(value) {
13729
13913
  }
13730
13914
  function readJson(filePath) {
13731
13915
  try {
13732
- return JSON.parse(fs15.readFileSync(filePath, "utf8"));
13916
+ return JSON.parse(fs16.readFileSync(filePath, "utf8"));
13733
13917
  } catch {
13734
13918
  return null;
13735
13919
  }
@@ -13748,7 +13932,7 @@ function readCodexPluginConfigured(filePath) {
13748
13932
  try {
13749
13933
  let currentPlugin = null;
13750
13934
  let inPluginsTable = false;
13751
- for (const line of fs15.readFileSync(filePath, "utf8").split(/\r?\n/)) {
13935
+ for (const line of fs16.readFileSync(filePath, "utf8").split(/\r?\n/)) {
13752
13936
  const section = /^\s*\[plugins\.(?:"([^"]+)"|'([^']+)'|(\S+))\]\s*(?:#.*)?$/.exec(line);
13753
13937
  if (section) {
13754
13938
  const captures = section.slice(1, 4);
@@ -13781,7 +13965,7 @@ function readCodexPluginConfigured(filePath) {
13781
13965
  }
13782
13966
  }
13783
13967
  function verifiedPluginVersion(root, client) {
13784
- if (client === "codex" && fs15.existsSync(path12.join(root, "plugin.json"))) {
13968
+ if (client === "codex" && fs16.existsSync(path12.join(root, "plugin.json"))) {
13785
13969
  const manifest2 = readJson(path12.join(root, "plugin.json"));
13786
13970
  if (manifest2 && typeof manifest2 === "object") {
13787
13971
  const plugin2 = manifest2;
@@ -13795,7 +13979,7 @@ function verifiedPluginVersion(root, client) {
13795
13979
  if (portableMcp.$schema !== AGENT_PLUGINS_MCP_SCHEMA || portableMcp.mcpServers?.canonry?.type !== "stdio" || portableMcp.mcpServers.canonry.command !== "canonry-mcp") {
13796
13980
  return null;
13797
13981
  }
13798
- return REQUIRED_SKILLS.every((name) => fs15.existsSync(path12.join(root, "skills", name, "SKILL.md"))) ? plugin2.version : null;
13982
+ return REQUIRED_SKILLS.every((name) => fs16.existsSync(path12.join(root, "skills", name, "SKILL.md"))) ? plugin2.version : null;
13799
13983
  }
13800
13984
  if (typeof plugin2.$schema === "string" && plugin2.$schema.startsWith("https://agent-plugins.org/schemas/")) {
13801
13985
  return null;
@@ -13813,7 +13997,7 @@ function verifiedPluginVersion(root, client) {
13813
13997
  if (!mcp || typeof mcp !== "object") return null;
13814
13998
  const canonryServer = mcp.mcpServers?.canonry;
13815
13999
  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;
14000
+ return REQUIRED_SKILLS.every((name) => fs16.existsSync(path12.join(root, "skills", name, "SKILL.md"))) ? plugin.version : null;
13817
14001
  }
13818
14002
  function readClaudeUserInstallPaths(claudeConfigDir) {
13819
14003
  const parsed = readJson(path12.join(claudeConfigDir, "plugins", "installed_plugins.json"));
@@ -13830,7 +14014,7 @@ function readClaudeUserInstallPaths(claudeConfigDir) {
13830
14014
  }
13831
14015
  function listDirectories(dir) {
13832
14016
  try {
13833
- return fs15.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path12.join(dir, entry.name));
14017
+ return fs16.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path12.join(dir, entry.name));
13834
14018
  } catch {
13835
14019
  return [];
13836
14020
  }
@@ -15483,7 +15667,7 @@ var VISIBILITY_STATS_CLI_COMMANDS = [
15483
15667
  ];
15484
15668
 
15485
15669
  // src/cli-commands/wordpress.ts
15486
- import fs16 from "fs";
15670
+ import fs17 from "fs";
15487
15671
 
15488
15672
  // src/commands/wordpress.ts
15489
15673
  function getClient28() {
@@ -15722,12 +15906,12 @@ async function wordpressSetMeta(project, body) {
15722
15906
  printPageDetail(result);
15723
15907
  }
15724
15908
  async function wordpressBulkSetMeta(project, opts) {
15725
- const fs18 = await import("fs/promises");
15909
+ const fs19 = await import("fs/promises");
15726
15910
  const path13 = await import("path");
15727
15911
  const filePath = path13.resolve(opts.from);
15728
15912
  let raw;
15729
15913
  try {
15730
- raw = await fs18.readFile(filePath, "utf8");
15914
+ raw = await fs19.readFile(filePath, "utf8");
15731
15915
  } catch {
15732
15916
  throw new CliError({
15733
15917
  code: "FILE_READ_ERROR",
@@ -15824,13 +16008,13 @@ async function wordpressSetSchema(project, body) {
15824
16008
  printManualAssist(`Schema update for "${body.slug}"`, result);
15825
16009
  }
15826
16010
  async function wordpressSchemaDeploy(project, opts) {
15827
- const fs18 = await import("fs/promises");
16011
+ const fs19 = await import("fs/promises");
15828
16012
  const path13 = await import("path");
15829
16013
  const yaml = await loadYamlModule();
15830
16014
  const filePath = path13.resolve(opts.profile);
15831
16015
  let raw;
15832
16016
  try {
15833
- raw = await fs18.readFile(filePath, "utf8");
16017
+ raw = await fs19.readFile(filePath, "utf8");
15834
16018
  } catch {
15835
16019
  throw new CliError({
15836
16020
  code: "FILE_READ_ERROR",
@@ -15935,13 +16119,13 @@ async function wordpressOnboard(project, opts) {
15935
16119
  }
15936
16120
  let profileData;
15937
16121
  if (opts.profile) {
15938
- const fs18 = await import("fs/promises");
16122
+ const fs19 = await import("fs/promises");
15939
16123
  const path13 = await import("path");
15940
16124
  const yaml = await loadYamlModule();
15941
16125
  const filePath = path13.resolve(opts.profile);
15942
16126
  let raw;
15943
16127
  try {
15944
- raw = await fs18.readFile(filePath, "utf8");
16128
+ raw = await fs19.readFile(filePath, "utf8");
15945
16129
  } catch {
15946
16130
  throw new CliError({
15947
16131
  code: "FILE_READ_ERROR",
@@ -16090,7 +16274,7 @@ function resolveContent(input, command, usage, options) {
16090
16274
  }
16091
16275
  if (contentFile) {
16092
16276
  try {
16093
- return fs16.readFileSync(contentFile, "utf-8");
16277
+ return fs17.readFileSync(contentFile, "utf-8");
16094
16278
  } catch (error) {
16095
16279
  const message = error instanceof Error ? error.message : String(error);
16096
16280
  throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {
@@ -17057,16 +17241,16 @@ Usage: ${usage}`, {
17057
17241
 
17058
17242
  // src/commands/measurement-plan.ts
17059
17243
  import crypto3 from "crypto";
17060
- import fs17 from "fs";
17244
+ import fs18 from "fs";
17061
17245
  import { parse } from "yaml";
17062
17246
  import { z as z2 } from "zod";
17063
17247
  function readPlan(source) {
17064
- const content = source === "-" ? fs17.readFileSync(0, "utf8") : fs17.readFileSync(source, "utf8");
17248
+ const content = source === "-" ? fs18.readFileSync(0, "utf8") : fs18.readFileSync(source, "utf8");
17065
17249
  const parsed = source.endsWith(".json") ? JSON.parse(content) : parse(content);
17066
17250
  return measurementPlanInputSchema.parse(parsed);
17067
17251
  }
17068
17252
  function readDiscoveryRule(source) {
17069
- const content = source === "-" ? fs17.readFileSync(0, "utf8") : fs17.readFileSync(source, "utf8");
17253
+ const content = source === "-" ? fs18.readFileSync(0, "utf8") : fs18.readFileSync(source, "utf8");
17070
17254
  const parsed = source.endsWith(".json") ? JSON.parse(content) : parse(content);
17071
17255
  return measurementDiscoveryRuleSchema.parse(parsed);
17072
17256
  }
@@ -17115,7 +17299,7 @@ var advancedDeactivateInputSchema = z2.object({
17115
17299
  }).strict();
17116
17300
  function readAdvancedMeasurementInput(source) {
17117
17301
  if (source === void 0) return {};
17118
- const content = source === "-" ? fs17.readFileSync(0, "utf8") : fs17.readFileSync(source, "utf8");
17302
+ const content = source === "-" ? fs18.readFileSync(0, "utf8") : fs18.readFileSync(source, "utf8");
17119
17303
  return JSON.parse(content);
17120
17304
  }
17121
17305
  var advancedMeasurementCollectionKeys = {
@@ -17297,7 +17481,7 @@ async function replaceMeasurementPlanAssignments(project, opts) {
17297
17481
  console.log(JSON.stringify({ preview, result }, null, 2));
17298
17482
  }
17299
17483
  function readGroupMembershipCsv(source) {
17300
- return source === "-" ? fs17.readFileSync(0, "utf8") : fs17.readFileSync(source, "utf8");
17484
+ return source === "-" ? fs18.readFileSync(0, "utf8") : fs18.readFileSync(source, "utf8");
17301
17485
  }
17302
17486
  async function previewMeasurementPlanGroups(project, source) {
17303
17487
  const preview = await createApiClient().previewMeasurementDraftGroupMembership(project, {