@canonry/canonry 4.177.3 → 4.178.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 (29) hide show
  1. package/assets/assets/{AuditHistoryPanel-YZvGU1la.js → AuditHistoryPanel-BmKdyRrR.js} +1 -1
  2. package/assets/assets/{BacklinksPage-CB2uJWWy.js → BacklinksPage-DtqKPVLV.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-BoYe2M-i.js → ChartPrimitives-B_LDVvTy.js} +1 -1
  4. package/assets/assets/{HistoryPage-D9IxxbQ9.js → HistoryPage-yyJsQiJQ.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-CxgnnPdp.js → MeasurementPropertyPage-hjW4iYtr.js} +1 -1
  6. package/assets/assets/ProjectPage-DZKpnPvq.js +9 -0
  7. package/assets/assets/{RunRow-2KxOta8F.js → RunRow-BZeBFb22.js} +1 -1
  8. package/assets/assets/{RunsPage-ya8vTkyd.js → RunsPage-tnXHsK_R.js} +1 -1
  9. package/assets/assets/{SettingsPage-lTy3o2bo.js → SettingsPage-Djj5k8vv.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-Cyu7MIv2.js → SiteHealthSection-BkDncS11.js} +3 -3
  11. package/assets/assets/{TrafficPage-DjhKfw57.js → TrafficPage-nJggXVh-.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-Pwi-Dl9Q.js → TrafficSourceDetailPage-zxONbSn-.js} +1 -1
  13. package/assets/assets/{extract-error-message-I1FVWHLH.js → extract-error-message-NpQu2vBm.js} +1 -1
  14. package/assets/assets/{index-DQVTRT9J.css → index-D0GbiVhZ.css} +1 -1
  15. package/assets/assets/index-Dwwnkd_V.js +83 -0
  16. package/assets/assets/{react-sigma_core.esm.min-BX6mOEq5.js → react-sigma_core.esm.min-CxMdd8U8.js} +1 -1
  17. package/assets/index.html +2 -2
  18. package/dist/{chunk-QRE3DAAU.js → chunk-A7FB2BVW.js} +2 -2
  19. package/dist/{chunk-7ABFIVKO.js → chunk-O7J5WFVZ.js} +361 -100
  20. package/dist/{chunk-6XUWDSLN.js → chunk-SXKA4OXZ.js} +1356 -907
  21. package/dist/{chunk-PQT3ECM3.js → chunk-T4JH77O6.js} +107 -0
  22. package/dist/{chunk-5EH666EO.js → chunk-YIQMTOEZ.js} +2970 -2054
  23. package/dist/cli.js +87 -5
  24. package/dist/index.js +4 -4
  25. package/dist/{intelligence-service-CHZGHFGS.js → intelligence-service-YISV327N.js} +2 -2
  26. package/dist/mcp.js +5 -380
  27. package/package.json +8 -8
  28. package/assets/assets/ProjectPage-CPmaXzhL.js +0 -9
  29. package/assets/assets/index-BeLVKN3H.js +0 -83
@@ -32,6 +32,7 @@ import {
32
32
  discoveryRunRequestSchema,
33
33
  gaMeasurementAnalysisWindowSchema,
34
34
  gaMeasurementHostScopeSchema,
35
+ googleAdsMetricsWindowSchema,
35
36
  gscPerformanceOrderBySchema,
36
37
  keywordBatchRequestSchema,
37
38
  keywordGenerateRequestSchema,
@@ -91,7 +92,7 @@ import {
91
92
  trafficConnectWordpressRequestSchema,
92
93
  trafficEventKindSchema,
93
94
  trafficSeriesGranularitySchema
94
- } from "./chunk-PQT3ECM3.js";
95
+ } from "./chunk-T4JH77O6.js";
95
96
 
96
97
  // src/config.ts
97
98
  import fs from "fs";
@@ -2959,6 +2960,18 @@ var postApiV1ProjectsByNameGoogleAdsSync = (options) => {
2959
2960
  ...options
2960
2961
  });
2961
2962
  };
2963
+ var getApiV1ProjectsByNameGoogleAdsPerformance = (options) => {
2964
+ return (options.client ?? client).get({
2965
+ security: [
2966
+ {
2967
+ scheme: "bearer",
2968
+ type: "http"
2969
+ }
2970
+ ],
2971
+ url: "/api/v1/projects/{name}/google-ads/performance",
2972
+ ...options
2973
+ });
2974
+ };
2962
2975
  var getApiV1ProjectsByNameGoogleAdsSnapshots = (options) => {
2963
2976
  return (options.client ?? client).get({
2964
2977
  security: [
@@ -3123,6 +3136,18 @@ var deleteApiV1ProjectsByNameGtmConnection = (options) => {
3123
3136
  ...options
3124
3137
  });
3125
3138
  };
3139
+ var getApiV1ProjectsByNameConversionTrackingOptions = (options) => {
3140
+ return (options.client ?? client).get({
3141
+ security: [
3142
+ {
3143
+ scheme: "bearer",
3144
+ type: "http"
3145
+ }
3146
+ ],
3147
+ url: "/api/v1/projects/{name}/conversion-tracking/options",
3148
+ ...options
3149
+ });
3150
+ };
3126
3151
  var getApiV1ProjectsByNameConversionTrackingContracts = (options) => {
3127
3152
  return (options.client ?? client).get({
3128
3153
  security: [
@@ -6813,6 +6838,23 @@ var ApiClient = class {
6813
6838
  })
6814
6839
  );
6815
6840
  }
6841
+ async getConversionTrackingOptions(project) {
6842
+ return this.invoke(
6843
+ () => getApiV1ProjectsByNameConversionTrackingOptions({
6844
+ client: this.heyClient,
6845
+ path: { name: project }
6846
+ })
6847
+ );
6848
+ }
6849
+ async getGoogleAdsPerformance(project, query) {
6850
+ return this.invoke(
6851
+ () => getApiV1ProjectsByNameGoogleAdsPerformance({
6852
+ client: this.heyClient,
6853
+ path: { name: project },
6854
+ ...query?.window ? { query: { window: query.window } } : {}
6855
+ })
6856
+ );
6857
+ }
6816
6858
  async listGoogleAdsSnapshots(project, query) {
6817
6859
  return this.invoke(
6818
6860
  () => getApiV1ProjectsByNameGoogleAdsSnapshots({
@@ -8376,973 +8418,599 @@ var ApiClient = class {
8376
8418
  }
8377
8419
  };
8378
8420
 
8421
+ // src/mcp/server.ts
8422
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8423
+ import { z as z4 } from "zod";
8424
+
8379
8425
  // src/package-version.ts
8380
8426
  import { createRequire } from "module";
8381
8427
  var _require = createRequire(import.meta.url);
8382
8428
  var PACKAGE_VERSION = _require("../package.json").version;
8383
8429
 
8384
- // src/commands/skills.ts
8385
- import crypto2 from "crypto";
8386
- import fs2 from "fs";
8387
- import os2 from "os";
8388
- import path2 from "path";
8389
- import { fileURLToPath } from "url";
8390
- var BUNDLED_SKILL_NAMES = ["canonry", "aero"];
8391
- function resolveBundledSkillsRoot(pkgDir) {
8392
- const here = pkgDir ?? path2.dirname(fileURLToPath(import.meta.url));
8393
- const candidates = [
8394
- path2.join(here, "../assets/agent-workspace/skills"),
8395
- path2.join(here, "../../assets/agent-workspace/skills"),
8396
- path2.join(here, "../../../../skills")
8397
- ];
8398
- for (const candidate of candidates) {
8399
- if (BUNDLED_SKILL_NAMES.every((name) => fs2.existsSync(path2.join(candidate, name, "SKILL.md")))) {
8400
- return candidate;
8401
- }
8402
- }
8403
- throw new CliError({
8404
- code: "INTERNAL_ERROR",
8405
- message: `Bundled skills not found. Searched:
8406
- ${candidates.join("\n ")}`,
8407
- exitCode: 2
8408
- });
8430
+ // src/mcp/tool-registry.ts
8431
+ import { z as z3 } from "zod";
8432
+
8433
+ // src/measurement-draft-actions.ts
8434
+ import { z } from "zod";
8435
+ var idempotencyKeySchema = z.string().trim().min(1).describe(
8436
+ "A fresh request key. Reuse it only when retrying the identical request."
8437
+ );
8438
+ var draftEtagSchema = z.string().trim().min(1).optional().describe(
8439
+ "Current draft ETag from canonry_measurement_draft_get. The API requires it for draft edits, publish, and discard; omit it only to receive the API\u2019s actionable 428 response."
8440
+ );
8441
+ function mutationOperationSchema(action, request) {
8442
+ return z.object({
8443
+ action: z.literal(action),
8444
+ request,
8445
+ etag: draftEtagSchema,
8446
+ idempotencyKey: idempotencyKeySchema
8447
+ }).strict().describe(`Operation for ${action}.`);
8409
8448
  }
8410
- function parseDescription(content) {
8411
- const fmMatch = /^---\n([\s\S]*?)\n---/.exec(content);
8412
- if (!fmMatch) return "";
8413
- const descMatch = /^description:\s*(\S.*)$/m.exec(fmMatch[1]);
8414
- if (!descMatch) return "";
8415
- return descMatch[1].replace(/^["']|["']$/g, "").trim();
8449
+ var measurementDraftOperationSchema = z.discriminatedUnion("action", [
8450
+ z.object({
8451
+ action: z.literal("create"),
8452
+ request: measurementDraftCreateRequestSchema,
8453
+ idempotencyKey: idempotencyKeySchema
8454
+ }).strict().describe("Operation for create."),
8455
+ mutationOperationSchema("import-sitemap", measurementDraftImportSitemapRequestSchema),
8456
+ mutationOperationSchema("apply-sitemap-selection", measurementDraftApplySitemapSelectionRequestSchema),
8457
+ mutationOperationSchema("upsert-target", measurementDraftUpsertTargetRequestSchema),
8458
+ mutationOperationSchema("rename-target", measurementDraftRenameTargetRequestSchema),
8459
+ mutationOperationSchema("merge-targets", measurementDraftMergeTargetsRequestSchema),
8460
+ mutationOperationSchema("exclude-target", measurementDraftExcludeTargetRequestSchema),
8461
+ mutationOperationSchema("rebind-target", measurementDraftRebindTargetRequestSchema),
8462
+ mutationOperationSchema("apply-assignments", measurementDraftApplyAssignmentsRequestSchema),
8463
+ z.object({
8464
+ action: z.literal("preview-assignments"),
8465
+ request: measurementDraftPreviewAssignmentsRequestSchema
8466
+ }).strict().describe("Read-semantic assignment impact preview."),
8467
+ mutationOperationSchema("replace-assignments", measurementDraftReplaceAssignmentsRequestSchema),
8468
+ mutationOperationSchema("apply-paired-assignments", measurementDraftApplyPairedAssignmentsRequestSchema),
8469
+ mutationOperationSchema("remove-assignment", measurementDraftRemoveAssignmentRequestSchema),
8470
+ mutationOperationSchema("clear-assignments", measurementDraftClearAssignmentsRequestSchema),
8471
+ mutationOperationSchema("classify-assignments", measurementDraftClassifyAssignmentsRequestSchema),
8472
+ mutationOperationSchema("upsert-group", measurementDraftUpsertGroupRequestSchema),
8473
+ mutationOperationSchema("remove-group", measurementDraftRemoveGroupRequestSchema),
8474
+ z.object({
8475
+ action: z.literal("preview-group-membership"),
8476
+ request: measurementDraftPreviewGroupMembershipRequestSchema
8477
+ }).strict().describe("Read-semantic CSV group-membership preview."),
8478
+ mutationOperationSchema("apply-group-membership", measurementDraftApplyGroupMembershipRequestSchema),
8479
+ mutationOperationSchema("upsert-competitor", measurementDraftUpsertCompetitorRequestSchema),
8480
+ mutationOperationSchema("remove-competitor", measurementDraftRemoveCompetitorRequestSchema),
8481
+ z.object({ action: z.literal("compile-preview") }).strict().describe("Operation for compile-preview."),
8482
+ z.object({ action: z.literal("diff-preview") }).strict().describe("Operation for diff-preview."),
8483
+ mutationOperationSchema("publish", measurementDraftPublishRequestSchema),
8484
+ z.object({
8485
+ action: z.literal("discard"),
8486
+ etag: draftEtagSchema,
8487
+ idempotencyKey: idempotencyKeySchema
8488
+ }).strict().describe("Operation for discard.")
8489
+ ]);
8490
+ function runMeasurementDraftAction(client2, project, actionInput) {
8491
+ switch (actionInput.action) {
8492
+ case "create":
8493
+ return client2.createMeasurementPlanDraft(project, actionInput.request, actionInput.idempotencyKey);
8494
+ case "import-sitemap":
8495
+ return client2.importMeasurementDraftSitemap(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8496
+ case "apply-sitemap-selection":
8497
+ return client2.applyMeasurementDraftSitemapSelection(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8498
+ case "upsert-target":
8499
+ return client2.upsertMeasurementDraftTarget(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8500
+ case "rename-target":
8501
+ return client2.renameMeasurementDraftTarget(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8502
+ case "merge-targets":
8503
+ return client2.mergeMeasurementDraftTargets(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8504
+ case "exclude-target":
8505
+ return client2.excludeMeasurementDraftTarget(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8506
+ case "rebind-target":
8507
+ return client2.rebindMeasurementDraftTarget(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8508
+ case "apply-assignments":
8509
+ return client2.applyMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8510
+ case "preview-assignments":
8511
+ return client2.previewMeasurementDraftAssignments(project, actionInput.request);
8512
+ case "replace-assignments":
8513
+ return client2.replaceMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8514
+ case "apply-paired-assignments":
8515
+ return client2.applyPairedMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8516
+ case "remove-assignment":
8517
+ return client2.removeMeasurementDraftAssignment(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8518
+ case "clear-assignments":
8519
+ return client2.clearMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8520
+ case "classify-assignments":
8521
+ return client2.classifyMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8522
+ case "upsert-group":
8523
+ return client2.upsertMeasurementDraftGroup(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8524
+ case "remove-group":
8525
+ return client2.removeMeasurementDraftGroup(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8526
+ case "preview-group-membership":
8527
+ return client2.previewMeasurementDraftGroupMembership(project, actionInput.request);
8528
+ case "apply-group-membership":
8529
+ return client2.applyMeasurementDraftGroupMembership(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8530
+ case "upsert-competitor":
8531
+ return client2.upsertMeasurementDraftCompetitor(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8532
+ case "remove-competitor":
8533
+ return client2.removeMeasurementDraftCompetitor(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8534
+ case "compile-preview":
8535
+ return client2.compileMeasurementDraftPreview(project);
8536
+ case "diff-preview":
8537
+ return client2.diffMeasurementDraftPreview(project);
8538
+ case "publish":
8539
+ return client2.publishMeasurementDraft(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8540
+ case "discard":
8541
+ return client2.discardMeasurementDraft(project, actionInput.idempotencyKey, actionInput.etag);
8542
+ }
8416
8543
  }
8417
- function getBundledSkills(pkgDir) {
8418
- const root = resolveBundledSkillsRoot(pkgDir);
8419
- return BUNDLED_SKILL_NAMES.map((name) => {
8420
- const skillDir = path2.join(root, name);
8421
- const skillFile = path2.join(skillDir, "SKILL.md");
8422
- const content = fs2.readFileSync(skillFile, "utf-8");
8423
- return { name, description: parseDescription(content), bundledPath: skillDir };
8424
- });
8544
+
8545
+ // src/mcp/schema.ts
8546
+ import { z as z2 } from "zod";
8547
+ var projectNameSchema = z2.string().min(1).describe("Canonry project name.");
8548
+ var runIdSchema = z2.string().min(1).describe("Canonry run ID.");
8549
+ var insightIdSchema = z2.string().min(1).describe("Canonry insight ID.");
8550
+ var analyticsWindowSchema = z2.enum(["7d", "30d", "90d", "all"]).describe("Analytics time window.");
8551
+ var emptyInputSchema = z2.object({});
8552
+ var projectInputSchema = z2.object({
8553
+ project: projectNameSchema
8554
+ });
8555
+ function toJsonSchema(schema, name) {
8556
+ return {
8557
+ ...z2.toJSONSchema(schema, { target: "draft-7" }),
8558
+ title: name
8559
+ };
8425
8560
  }
8426
- function walkRelative(dir, prefix = "") {
8427
- const out = [];
8428
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
8429
- const rel = prefix ? path2.join(prefix, entry.name) : entry.name;
8430
- const full = path2.join(dir, entry.name);
8431
- if (entry.isDirectory()) {
8432
- out.push(...walkRelative(full, rel));
8433
- } else if (entry.isFile()) {
8434
- out.push(rel);
8561
+ function compactStringParams(values, keys) {
8562
+ const params = {};
8563
+ for (const key of keys) {
8564
+ const value = values[key];
8565
+ if (value === void 0 || value === null || value === "") continue;
8566
+ if (typeof value === "string") {
8567
+ params[key] = value;
8568
+ } else if (typeof value === "number" || typeof value === "boolean") {
8569
+ params[key] = String(value);
8435
8570
  }
8436
8571
  }
8437
- return out.sort();
8438
- }
8439
- function sha256File(filePath) {
8440
- return crypto2.createHash("sha256").update(fs2.readFileSync(filePath)).digest("hex");
8572
+ return Object.keys(params).length ? params : void 0;
8441
8573
  }
8442
- function readSkillManifest(skillDir) {
8443
- try {
8444
- return coerceSkillManifest(JSON.parse(fs2.readFileSync(path2.join(skillDir, SKILL_MANIFEST_FILENAME), "utf-8")));
8445
- } catch {
8446
- return null;
8574
+ function uniqueStrings(values) {
8575
+ const seen = /* @__PURE__ */ new Set();
8576
+ const result = [];
8577
+ for (const value of values) {
8578
+ const trimmed = value.trim();
8579
+ if (!trimmed || seen.has(trimmed)) continue;
8580
+ seen.add(trimmed);
8581
+ result.push(trimmed);
8447
8582
  }
8583
+ return result;
8448
8584
  }
8449
- function writeSkillManifest(skillDir, manifest) {
8450
- fs2.writeFileSync(path2.join(skillDir, SKILL_MANIFEST_FILENAME), `${JSON.stringify(manifest, null, 2)}
8451
- `, "utf-8");
8452
- }
8453
- function reconcileSkillTree(srcDir, destDir, manifest, force) {
8454
- const result = { added: [], updated: [], unchanged: [], conflicts: [], bundledHashes: /* @__PURE__ */ new Map() };
8455
- for (const rel of walkRelative(srcDir)) {
8456
- const srcPath = path2.join(srcDir, rel);
8457
- const destPath = path2.join(destDir, rel);
8458
- const bundledHash = sha256File(srcPath);
8459
- result.bundledHashes.set(rel, bundledHash);
8460
- const installedHash = fs2.existsSync(destPath) ? sha256File(destPath) : void 0;
8461
- const state = classifySkillFile({ bundledHash, installedHash, manifestHash: manifest?.files[rel] });
8462
- switch (state) {
8463
- case "missing":
8464
- fs2.mkdirSync(path2.dirname(destPath), { recursive: true });
8465
- fs2.copyFileSync(srcPath, destPath);
8466
- result.added.push(rel);
8467
- break;
8468
- case "unchanged":
8469
- result.unchanged.push(rel);
8470
- break;
8471
- case "stale":
8472
- fs2.copyFileSync(srcPath, destPath);
8473
- result.updated.push(rel);
8474
- break;
8475
- case "edited":
8476
- if (force) {
8477
- fs2.copyFileSync(srcPath, destPath);
8478
- result.updated.push(rel);
8479
- } else {
8480
- result.conflicts.push(rel);
8481
- }
8482
- break;
8483
- }
8484
- }
8485
- return result;
8486
- }
8487
- function buildManifest(skillName, recon, prior) {
8488
- const conflicts = new Set(recon.conflicts);
8489
- const files = {};
8490
- for (const [rel, bundledHash] of recon.bundledHashes) {
8491
- files[rel] = conflicts.has(rel) ? prior?.files[rel] ?? bundledHash : bundledHash;
8492
- }
8493
- return { skill: skillName, version: PACKAGE_VERSION, files };
8494
- }
8495
- function describeChanges(recon) {
8496
- const parts = [];
8497
- if (recon.added.length > 0) parts.push(`${recon.added.length} added`);
8498
- if (recon.updated.length > 0) parts.push(`${recon.updated.length} refreshed`);
8499
- return parts.length > 0 ? parts.join(", ") : "no changes";
8500
- }
8501
- function buildClaudeMessage(name, status, recon) {
8502
- const rel = `.claude/skills/${name}`;
8503
- let message = status === "installed" ? `Installed ${rel}` : status === "updated" ? `Updated ${rel} (${describeChanges(recon)})` : `Already installed: ${rel}`;
8504
- if (recon.conflicts.length > 0) {
8505
- message += ` \u2014 ${recon.conflicts.length} file(s) differ from the bundle (local edits kept; pass --force to overwrite)`;
8506
- }
8507
- return message;
8508
- }
8509
- function installClaudeSkill(skill, targetDir, force) {
8510
- const targetPath = path2.join(targetDir, ".claude", "skills", skill.name);
8511
- const existedBefore = fs2.existsSync(path2.join(targetPath, "SKILL.md"));
8512
- const priorManifest = readSkillManifest(targetPath);
8513
- fs2.mkdirSync(targetPath, { recursive: true });
8514
- const recon = reconcileSkillTree(skill.bundledPath, targetPath, priorManifest, force);
8515
- writeSkillManifest(targetPath, buildManifest(skill.name, recon, priorManifest));
8516
- const changed = recon.added.length + recon.updated.length;
8517
- const status = !existedBefore ? "installed" : changed > 0 ? "updated" : "already-installed";
8518
- return {
8519
- skill: skill.name,
8520
- client: CodingAgents.claude,
8521
- targetPath,
8522
- status,
8523
- message: buildClaudeMessage(skill.name, status, recon),
8524
- added: recon.added,
8525
- updated: recon.updated,
8526
- unchanged: recon.unchanged,
8527
- conflicts: recon.conflicts
8528
- };
8529
- }
8530
- function installCodexSymlink(skill, targetDir, force) {
8531
- const codexPath = path2.join(targetDir, ".codex", "skills", skill.name);
8532
- const claudePath = path2.join(targetDir, ".claude", "skills", skill.name);
8533
- const linkTarget = path2.relative(path2.dirname(codexPath), claudePath);
8534
- fs2.mkdirSync(path2.dirname(codexPath), { recursive: true });
8535
- let stat;
8536
- try {
8537
- stat = fs2.lstatSync(codexPath);
8538
- } catch {
8539
- stat = void 0;
8540
- }
8541
- if (stat?.isSymbolicLink()) {
8542
- const existing = fs2.readlinkSync(codexPath);
8543
- if (existing === linkTarget) {
8544
- return {
8545
- skill: skill.name,
8546
- client: CodingAgents.codex,
8547
- targetPath: codexPath,
8548
- status: "already-linked",
8549
- message: `Already linked: .codex/skills/${skill.name}`
8550
- };
8551
- }
8552
- if (!force) {
8553
- throw new CliError({
8554
- code: "VALIDATION_ERROR",
8555
- message: `.codex/skills/${skill.name} is a symlink pointing elsewhere (${existing}). Pass --force to relink.`,
8556
- details: { skill: skill.name, targetPath: codexPath, existingTarget: existing },
8557
- exitCode: 1
8558
- });
8559
- }
8560
- fs2.unlinkSync(codexPath);
8561
- fs2.symlinkSync(linkTarget, codexPath);
8562
- return {
8563
- skill: skill.name,
8564
- client: CodingAgents.codex,
8565
- targetPath: codexPath,
8566
- status: "relinked",
8567
- message: `Relinked .codex/skills/${skill.name} \u2192 ${linkTarget}`
8568
- };
8569
- }
8570
- if (stat) {
8571
- if (!force) {
8572
- throw new CliError({
8573
- code: "VALIDATION_ERROR",
8574
- message: `.codex/skills/${skill.name} exists but is not a symlink. Pass --force to replace.`,
8575
- details: { skill: skill.name, targetPath: codexPath },
8576
- exitCode: 1
8577
- });
8578
- }
8579
- fs2.rmSync(codexPath, { recursive: true, force: true });
8580
- }
8581
- fs2.symlinkSync(linkTarget, codexPath);
8585
+
8586
+ // src/mcp/tool-registry.ts
8587
+ var readAnnotations = (openWorldHint) => ({
8588
+ readOnlyHint: true,
8589
+ ...openWorldHint ? { openWorldHint } : {}
8590
+ });
8591
+ var writeAnnotations = (opts) => ({
8592
+ readOnlyHint: false,
8593
+ idempotentHint: opts.idempotentHint,
8594
+ destructiveHint: Boolean(opts.destructiveHint),
8595
+ ...opts.openWorldHint ? { openWorldHint: opts.openWorldHint } : {}
8596
+ });
8597
+ function defineTool(tool) {
8582
8598
  return {
8583
- skill: skill.name,
8584
- client: CodingAgents.codex,
8585
- targetPath: codexPath,
8586
- status: stat ? "relinked" : "linked",
8587
- message: stat ? `Replaced and linked .codex/skills/${skill.name} \u2192 ${linkTarget}` : `Linked .codex/skills/${skill.name} \u2192 ${linkTarget}`
8599
+ ...tool,
8600
+ inputJsonSchema: toJsonSchema(tool.inputSchema, tool.name)
8588
8601
  };
8589
8602
  }
8590
- function buildSummaryMessage(results) {
8591
- const counts = {};
8592
- for (const r of results) counts[r.status] = (counts[r.status] ?? 0) + 1;
8593
- const parts = Object.entries(counts).map(([status, n]) => `${n} ${status}`);
8594
- let message = `Skills install summary: ${parts.join(", ")}.`;
8595
- const totalConflicts = results.reduce((sum, r) => sum + (r.conflicts?.length ?? 0), 0);
8596
- if (totalConflicts > 0) {
8597
- message += ` ${totalConflicts} file(s) differ from the bundle and were kept \u2014 pass --force to overwrite local edits.`;
8598
- }
8599
- return message;
8600
- }
8601
- function getBundledSkillSnapshots(pkgDir) {
8602
- return getBundledSkills(pkgDir).map((skill) => {
8603
- const files = {};
8604
- for (const rel of walkRelative(skill.bundledPath)) {
8605
- files[rel] = sha256File(path2.join(skill.bundledPath, rel));
8606
- }
8607
- return {
8608
- name: skill.name,
8609
- version: PACKAGE_VERSION,
8610
- files,
8611
- description: readBundledSkillDescription(skill.bundledPath)
8612
- };
8613
- });
8614
- }
8615
- function readBundledSkillDescription(skillDir) {
8616
- let raw;
8617
- try {
8618
- raw = fs2.readFileSync(path2.join(skillDir, "SKILL.md"), "utf-8");
8619
- } catch {
8620
- return void 0;
8603
+ var runTriggerInputSchema = z3.object({
8604
+ project: projectNameSchema,
8605
+ request: runTriggerRequestSchema.optional()
8606
+ });
8607
+ var measurementPlanVersionInputSchema = z3.object({ project: projectNameSchema, revision: z3.number().int().positive() });
8608
+ var measurementReportInputSchema = measurementPlanVersionInputSchema.extend({
8609
+ runId: runIdSchema.optional().describe("Exact eligible full measurement run to reconstruct. Omit for the latest run in the revision.")
8610
+ });
8611
+ var measurementPlanPreviewInputSchema = z3.object({ project: projectNameSchema, plan: measurementPlanAuthoringSchema });
8612
+ var measurementPlanPublishInputSchema = measurementPlanPublishRequestSchema.extend({ project: projectNameSchema });
8613
+ var measurementPlanRetireInputSchema = z3.object({ project: projectNameSchema, stableKey: z3.string().min(1) });
8614
+ var measurementDiscoveryInputSchema = measurementDiscoveryRequestSchema.extend({ project: projectNameSchema });
8615
+ var idempotencyKeyInputSchema = z3.string().trim().min(1).describe("A fresh request key. Reuse it only when retrying the identical request.");
8616
+ var measurementOverviewInputSchema = z3.object({
8617
+ project: projectNameSchema,
8618
+ scope: measurementOverviewQuerySchema.shape.scope.describe("Read all Properties, one reporting group, or one Property."),
8619
+ groupKey: measurementOverviewQuerySchema.shape.groupKey.describe("Group stable key. Required only for group scope."),
8620
+ targetKey: measurementOverviewQuerySchema.shape.targetKey.describe("Property stable key. Required only for property scope."),
8621
+ queryClass: measurementOverviewQuerySchema.shape.queryClass,
8622
+ provider: measurementOverviewQuerySchema.shape.provider,
8623
+ location: measurementOverviewQuerySchema.shape.location,
8624
+ from: measurementOverviewQuerySchema.shape.from,
8625
+ to: measurementOverviewQuerySchema.shape.to,
8626
+ runId: measurementOverviewQuerySchema.shape.runId,
8627
+ search: measurementOverviewQuerySchema.shape.search,
8628
+ sort: measurementOverviewQuerySchema.shape.sort,
8629
+ cursor: measurementOverviewQuerySchema.shape.cursor,
8630
+ limit: measurementOverviewQuerySchema.shape.limit
8631
+ }).strict().superRefine((input, context) => {
8632
+ if (input.scope === "group" && !input.groupKey) {
8633
+ context.addIssue({ code: "custom", path: ["groupKey"], message: "Group scope requires groupKey." });
8621
8634
  }
8622
- const parts = raw.split("---");
8623
- if (parts.length < 3) return void 0;
8624
- const lines = (parts[1] ?? "").split("\n");
8625
- const collected = [];
8626
- let inside = false;
8627
- for (const line of lines) {
8628
- const isTopLevelKey = /^[a-z][\w-]*:/i.test(line);
8629
- if (inside && isTopLevelKey) break;
8630
- if (isTopLevelKey && line.startsWith("description:")) {
8631
- inside = true;
8632
- collected.push(line.slice("description:".length).trim());
8633
- continue;
8634
- }
8635
- if (inside) collected.push(line.trim());
8635
+ if (input.scope === "property" && !input.targetKey) {
8636
+ context.addIssue({ code: "custom", path: ["targetKey"], message: "Property scope requires targetKey." });
8636
8637
  }
8637
- if (!inside) return void 0;
8638
- const value = collected.join(" ").trim();
8639
- const unquoted = value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
8640
- return unquoted.length > 0 ? unquoted : void 0;
8641
- }
8642
- async function installSkills(opts = {}) {
8643
- const targetDir = opts.user ? os2.homedir() : path2.resolve(opts.dir ?? process.cwd());
8644
- const client2 = opts.client ?? SkillsClients.all;
8645
- const force = opts.force ?? false;
8646
- const allSkills = getBundledSkills();
8647
- const requestedNames = opts.skills && opts.skills.length > 0 ? opts.skills : allSkills.map((s) => s.name);
8648
- const knownNames = new Set(allSkills.map((s) => s.name));
8649
- const unknown = requestedNames.filter((n) => !knownNames.has(n));
8650
- if (unknown.length > 0) {
8651
- throw new CliError({
8652
- code: "VALIDATION_ERROR",
8653
- message: `Unknown skill(s): ${unknown.join(", ")}. Available: ${[...knownNames].join(", ")}`,
8654
- details: { unknownSkills: unknown, availableSkills: [...knownNames] },
8655
- exitCode: 1
8656
- });
8638
+ if (input.scope !== "group" && input.groupKey) {
8639
+ context.addIssue({ code: "custom", path: ["groupKey"], message: `${input.scope} scope does not accept groupKey.` });
8657
8640
  }
8658
- const skillsToInstall = allSkills.filter((s) => requestedNames.includes(s.name));
8659
- fs2.mkdirSync(targetDir, { recursive: true });
8660
- const results = [];
8661
- for (const skill of skillsToInstall) {
8662
- results.push(installClaudeSkill(skill, targetDir, force));
8663
- if (client2 !== SkillsClients.claude) {
8664
- results.push(installCodexSymlink(skill, targetDir, force));
8665
- }
8641
+ if (input.scope !== "property" && input.targetKey) {
8642
+ context.addIssue({ code: "custom", path: ["targetKey"], message: `${input.scope} scope does not accept targetKey.` });
8666
8643
  }
8667
- return {
8668
- targetDir,
8669
- results,
8670
- message: buildSummaryMessage(results)
8671
- };
8672
- }
8673
- async function listSkills(opts = {}) {
8674
- const skills = getBundledSkills();
8675
- if (isMachineFormat(opts.format)) {
8676
- console.log(JSON.stringify({
8677
- skills: skills.map((s) => ({
8678
- name: s.name,
8679
- description: s.description,
8680
- claudePath: `.claude/skills/${s.name}`,
8681
- codexPath: `.codex/skills/${s.name}`
8682
- }))
8683
- }, null, 2));
8684
- return;
8644
+ });
8645
+ var measurementPropertyEvidenceInputSchema = measurementPropertyEvidenceQuerySchema.extend({
8646
+ project: projectNameSchema,
8647
+ targetKey: measurementPropertyEvidenceQuerySchema.shape.targetKey.describe("Property stable key. Required \u2014 this read is scoped to exactly one Property."),
8648
+ shape: measurementPropertyEvidenceQuerySchema.shape.shape.describe(
8649
+ "What one row is. Omit for sources (one row per cited URL). answers gives one row per measured answer with its cited URLs nested, including the answers that cited nothing."
8650
+ )
8651
+ }).strict();
8652
+ var measurementPortfolioSummaryInputSchema = measurementPortfolioSummaryQuerySchema.extend({
8653
+ project: projectNameSchema
8654
+ }).strict();
8655
+ var measurementPropertyQuestionsInputSchema = measurementPropertyQuestionsQuerySchema.extend({
8656
+ project: projectNameSchema
8657
+ }).strict();
8658
+ var measurementQuestionResultInputSchema = measurementQuestionResultQuerySchema.extend({
8659
+ project: projectNameSchema
8660
+ }).strict();
8661
+ var measurementPropertyCompetitorsInputSchema = measurementPropertyCompetitorsQuerySchema.extend({
8662
+ project: projectNameSchema
8663
+ }).strict();
8664
+ var measurementChangesInputSchema = measurementChangesQuerySchema.extend({
8665
+ project: projectNameSchema
8666
+ }).strict();
8667
+ var measurementDataQualityInputSchema = measurementDataQualityQuerySchema.extend({
8668
+ project: projectNameSchema
8669
+ }).strict();
8670
+ var measurementDraftCollectionInputSchema = measurementDraftCollectionQuerySchema.extend({ project: projectNameSchema });
8671
+ var measurementQuerySetInputSchema = z3.object({
8672
+ project: projectNameSchema,
8673
+ setId: z3.string().trim().min(1)
8674
+ }).strict();
8675
+ var measurementQuerySetUpsertInputSchema = measurementQuerySetInputSchema.extend({
8676
+ request: measurementQuerySetUpsertRequestSchema
8677
+ }).strict();
8678
+ var measurementQueryTemplateInputSchema = z3.object({
8679
+ project: projectNameSchema,
8680
+ templateId: z3.string().trim().min(1)
8681
+ }).strict();
8682
+ var measurementQueryTemplateUpsertInputSchema = measurementQueryTemplateInputSchema.extend({
8683
+ request: measurementQueryTemplateUpsertRequestSchema
8684
+ }).strict();
8685
+ var measurementQueryTemplateApplyInputSchema = measurementQueryTemplateInputSchema.extend({
8686
+ request: measurementQueryTemplateApplyRequestSchema,
8687
+ idempotencyKey: idempotencyKeyInputSchema
8688
+ }).strict();
8689
+ var measurementPlanDeactivateInputSchema = measurementPlanDeactivateRequestSchema.extend({
8690
+ project: projectNameSchema,
8691
+ idempotencyKey: idempotencyKeyInputSchema
8692
+ }).strict();
8693
+ var measurementDraftActionInputSchema = z3.object({
8694
+ project: projectNameSchema,
8695
+ operation: measurementDraftOperationSchema.describe("Typed draft operation. Select exactly one action branch.")
8696
+ }).strict();
8697
+ var measurementDraftActionOpenApiOperations = [
8698
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/create",
8699
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/import-sitemap",
8700
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-sitemap-selection",
8701
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-target",
8702
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/rename-target",
8703
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/merge-targets",
8704
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/exclude-target",
8705
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/rebind-target",
8706
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-assignments",
8707
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/preview-assignments",
8708
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/replace-assignments",
8709
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-paired-assignments",
8710
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-assignment",
8711
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/clear-assignments",
8712
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/classify-assignments",
8713
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-group",
8714
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-group",
8715
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/preview-group-membership",
8716
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-group-membership",
8717
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-competitor",
8718
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-competitor",
8719
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/compile-preview",
8720
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/diff-preview",
8721
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/publish",
8722
+ "POST /api/v1/projects/{name}/measurement-plan/draft/actions/discard"
8723
+ ];
8724
+ var runsListInputSchema = z3.object({
8725
+ project: projectNameSchema,
8726
+ limit: z3.number().int().positive().max(500).optional()
8727
+ });
8728
+ var runGetInputSchema = z3.object({
8729
+ runId: runIdSchema
8730
+ });
8731
+ var timelineInputSchema = z3.object({
8732
+ project: projectNameSchema,
8733
+ location: z3.string().optional().describe("Location label. Use an empty string for locationless results."),
8734
+ limit: z3.number().int().positive().max(100).optional().describe("Restrict history to the most recent N project runs.")
8735
+ });
8736
+ var historyFilterShape = {
8737
+ limit: z3.number().int().positive().max(500).optional(),
8738
+ offset: z3.number().int().nonnegative().optional(),
8739
+ since: z3.string().optional().describe("ISO 8601 lower bound."),
8740
+ action: z3.string().optional().describe("Exact audit action filter."),
8741
+ actor: z3.string().optional().describe("Exact actor filter."),
8742
+ entityType: z3.string().optional().describe("Exact entity type filter.")
8743
+ };
8744
+ var projectHistoryInputSchema = z3.object({ project: projectNameSchema, ...historyFilterShape });
8745
+ var globalHistoryInputSchema = z3.object(historyFilterShape);
8746
+ var snapshotsListInputSchema = z3.object({
8747
+ project: projectNameSchema,
8748
+ limit: z3.number().int().positive().max(500).optional(),
8749
+ offset: z3.number().int().nonnegative().optional(),
8750
+ location: z3.string().optional().describe("Location label. Use an empty string for locationless results.")
8751
+ });
8752
+ var snapshotsDiffInputSchema = z3.object({
8753
+ project: projectNameSchema,
8754
+ run1: runIdSchema,
8755
+ run2: runIdSchema
8756
+ });
8757
+ var insightsListInputSchema = z3.object({
8758
+ project: projectNameSchema,
8759
+ dismissed: z3.boolean().optional(),
8760
+ runId: runIdSchema.optional()
8761
+ });
8762
+ var insightInputSchema = z3.object({
8763
+ project: projectNameSchema,
8764
+ insightId: insightIdSchema
8765
+ });
8766
+ var healthHistoryInputSchema = z3.object({
8767
+ project: projectNameSchema,
8768
+ limit: z3.number().int().positive().max(100).optional()
8769
+ });
8770
+ var gscPerformanceInputSchema = z3.object({
8771
+ project: projectNameSchema,
8772
+ startDate: z3.string().optional(),
8773
+ endDate: z3.string().optional(),
8774
+ query: z3.string().optional(),
8775
+ page: z3.string().optional(),
8776
+ limit: z3.number().int().positive().max(5e3).optional(),
8777
+ offset: z3.number().int().nonnegative().optional(),
8778
+ orderBy: gscPerformanceOrderBySchema.optional(),
8779
+ window: analyticsWindowSchema.optional()
8780
+ });
8781
+ var gscPerformanceDailyInputSchema = z3.object({
8782
+ project: projectNameSchema,
8783
+ startDate: z3.string().optional(),
8784
+ endDate: z3.string().optional(),
8785
+ window: analyticsWindowSchema.optional()
8786
+ });
8787
+ var gscTopPagesInputSchema = z3.object({
8788
+ project: projectNameSchema,
8789
+ startDate: z3.string().optional(),
8790
+ endDate: z3.string().optional(),
8791
+ limit: z3.number().int().positive().max(500).optional(),
8792
+ window: analyticsWindowSchema.optional()
8793
+ });
8794
+ var gscInspectionsInputSchema = z3.object({
8795
+ project: projectNameSchema,
8796
+ url: z3.string().optional(),
8797
+ limit: z3.number().int().positive().max(500).optional()
8798
+ });
8799
+ var gscCoverageHistoryInputSchema = z3.object({
8800
+ project: projectNameSchema,
8801
+ limit: z3.number().int().positive().max(500).optional()
8802
+ });
8803
+ var gscSitemapsInputSchema = z3.object({
8804
+ project: projectNameSchema,
8805
+ sitemapIndex: z3.string().url().optional()
8806
+ });
8807
+ var gscSitemapsSubmitInputSchema = z3.union([
8808
+ z3.object({
8809
+ project: projectNameSchema,
8810
+ sitemapUrls: z3.array(z3.string().url()).min(1).max(50)
8811
+ }).strict(),
8812
+ z3.object({
8813
+ project: projectNameSchema,
8814
+ mode: z3.enum(["indexes", "all-files"])
8815
+ }).strict()
8816
+ ]);
8817
+ async function submitGscSitemapsFromMcp(client2, input) {
8818
+ let sitemapUrls;
8819
+ if ("sitemapUrls" in input) {
8820
+ sitemapUrls = uniqueStrings(input.sitemapUrls);
8821
+ } else {
8822
+ const topLevel = await client2.gscSitemaps(input.project);
8823
+ if (input.mode === "indexes") {
8824
+ sitemapUrls = uniqueStrings(
8825
+ topLevel.preferredSubmissionUrls.length > 0 ? topLevel.preferredSubmissionUrls : topLevel.sitemaps.map((sitemap) => sitemap.path)
8826
+ );
8827
+ } else {
8828
+ const indexes = topLevel.sitemaps.filter((sitemap) => sitemap.isSitemapsIndex).map((sitemap) => sitemap.path);
8829
+ const expandedIndexUrls = [];
8830
+ for (let offset = 0; offset < indexes.length; offset += 4) {
8831
+ const children = await Promise.all(
8832
+ indexes.slice(offset, offset + 4).map(
8833
+ (sitemapIndex) => client2.gscSitemaps(input.project, { sitemapIndex })
8834
+ )
8835
+ );
8836
+ children.forEach((result, index) => {
8837
+ const sitemapIndex = indexes[offset + index];
8838
+ expandedIndexUrls.push(...result.sitemaps.length > 0 ? result.sitemaps.map((sitemap) => sitemap.path) : [sitemapIndex]);
8839
+ });
8840
+ }
8841
+ sitemapUrls = uniqueStrings([
8842
+ ...topLevel.sitemaps.filter((sitemap) => !sitemap.isSitemapsIndex).map((sitemap) => sitemap.path),
8843
+ ...expandedIndexUrls
8844
+ ]);
8845
+ }
8685
8846
  }
8686
- console.log("Bundled canonry skills:\n");
8687
- for (const skill of skills) {
8688
- console.log(` ${skill.name}`);
8689
- if (skill.description) console.log(` ${skill.description}`);
8690
- console.log(` Claude: .claude/skills/${skill.name}/`);
8691
- console.log(` Codex: .codex/skills/${skill.name} (symlink \u2192 ../../.claude/skills/${skill.name})`);
8692
- console.log();
8847
+ if (sitemapUrls.length === 0) {
8848
+ throw new Error("No GSC sitemaps found. Submit an explicit sitemap URL first.");
8849
+ }
8850
+ const aggregate = {
8851
+ summary: { total: 0, accepted: 0, failed: 0 },
8852
+ results: []
8853
+ };
8854
+ for (let offset = 0; offset < sitemapUrls.length; offset += 50) {
8855
+ const batchSitemapUrls = sitemapUrls.slice(offset, offset + 50);
8856
+ try {
8857
+ const result = await client2.gscSubmitSitemaps(input.project, {
8858
+ sitemapUrls: batchSitemapUrls
8859
+ });
8860
+ aggregate.summary.total += result.summary.total;
8861
+ aggregate.summary.accepted += result.summary.accepted;
8862
+ aggregate.summary.failed += result.summary.failed;
8863
+ aggregate.results.push(...result.results);
8864
+ } catch (cause) {
8865
+ if (offset === 0) throw cause;
8866
+ const attempted = aggregate.summary.total + batchSitemapUrls.length;
8867
+ const remaining = sitemapUrls.length - attempted;
8868
+ throw new CliError({
8869
+ code: "GOOGLE_SITEMAP_SUBMISSION_PARTIAL",
8870
+ message: `Sitemap submission stopped after ${aggregate.summary.total} completed results; ${batchSitemapUrls.length} are unconfirmed and ${remaining} were not attempted.`,
8871
+ exitCode: cause instanceof CliError && cause.exitCode === 1 ? 1 : EXIT_SYSTEM_ERROR,
8872
+ details: {
8873
+ project: input.project,
8874
+ accepted: aggregate.summary.accepted,
8875
+ failed: aggregate.summary.failed,
8876
+ completed: aggregate.summary.total,
8877
+ attempted,
8878
+ unconfirmed: batchSitemapUrls.length,
8879
+ remaining,
8880
+ unconfirmedBatch: { index: Math.floor(offset / 50) + 1, sitemapUrls: batchSitemapUrls },
8881
+ partialResult: aggregate,
8882
+ cause: cause instanceof CliError ? { code: cause.code, message: cause.message, details: cause.details } : { message: describeError(cause) }
8883
+ }
8884
+ });
8885
+ }
8693
8886
  }
8887
+ return aggregate;
8694
8888
  }
8695
- function emitInstallSummary(summary, format) {
8696
- if (isMachineFormat(format)) {
8697
- console.log(JSON.stringify(summary, null, 2));
8698
- return;
8699
- }
8700
- for (const r of summary.results) console.log(r.message);
8701
- console.log(`
8702
- Target: ${summary.targetDir}`);
8703
- console.log(summary.message);
8704
- }
8705
- function getMissingUserSkillsNudge(home, agentPlugin) {
8706
- if (!home) return null;
8707
- const skillsBase = path2.join(home, ".claude", "skills");
8708
- const installed = [];
8709
- const missing = [];
8710
- for (const name of BUNDLED_SKILL_NAMES) {
8711
- const skillFile = path2.join(skillsBase, name, "SKILL.md");
8712
- if (existsSafe(skillFile)) installed.push(name);
8713
- else missing.push(name);
8714
- }
8715
- if (agentPlugin && agentPlugin.configuredClients.length > 0) {
8716
- const unverifiedClients = agentPlugin.configuredClients.filter((client2) => !agentPlugin.verifiedClients.includes(client2));
8717
- const mismatchedClients = agentPlugin.verifiedClients.filter((client2) => agentPlugin.verifiedClientVersions?.[client2] !== PACKAGE_VERSION);
8718
- if (unverifiedClients.length === 0 && mismatchedClients.length === 0) return null;
8719
- const displayClients = (clients) => clients.map((client2) => client2 === "claude-code" ? "Claude Code" : "Codex").join(" + ");
8720
- const problems = [];
8721
- if (unverifiedClients.length > 0) {
8722
- problems.push(`The Canonry plugin is enabled for ${displayClients(unverifiedClients)}, but its cached manifest and skill assets could not be verified.`);
8723
- }
8724
- if (mismatchedClients.length > 0) {
8725
- const versions = mismatchedClients.map((client2) => `${client2 === "claude-code" ? "Claude Code" : "Codex"} v${agentPlugin.verifiedClientVersions?.[client2] ?? "unknown"}`).join(", ");
8726
- problems.push(`${versions} ${mismatchedClients.length === 1 ? "does" : "do"} not match the running Canonry v${PACKAGE_VERSION}.`);
8727
- }
8728
- return {
8729
- message: `Tip: ${problems.join(" ")} Update or reinstall \`canonry@canonry\` with the affected client plugin manager.`,
8730
- missing,
8731
- installed
8732
- };
8733
- }
8734
- if (missing.length === 0) return null;
8735
- const fix = missing.length === BUNDLED_SKILL_NAMES.length ? "canonry skills install --user" : `canonry skills install ${missing.join(" ")} --user`;
8736
- return {
8737
- message: `Tip: ${missing.join(" + ")} skill${missing.length === 1 ? "" : "s"} not installed in ~/.claude/skills/. Run \`${fix}\` so Claude/Codex sessions on this host auto-load the canonry reference docs.`,
8738
- missing,
8739
- installed
8740
- };
8741
- }
8742
- function existsSafe(p) {
8743
- try {
8744
- return fs2.existsSync(p);
8745
- } catch {
8746
- return false;
8747
- }
8748
- }
8749
- function parseSkillsClient(value) {
8750
- if (!value) return SkillsClients.all;
8751
- const parsed = skillsClientSchema.safeParse(value);
8752
- if (parsed.success) return parsed.data;
8753
- const allowed = skillsClientSchema.options;
8754
- throw new CliError({
8755
- code: "VALIDATION_ERROR",
8756
- message: `Invalid --client value "${value}". Must be one of: ${allowed.join(", ")}`,
8757
- details: { flag: "client", value, allowed },
8758
- exitCode: 1
8759
- });
8760
- }
8761
-
8762
- // src/mcp/tool-registry.ts
8763
- import { z as z3 } from "zod";
8764
-
8765
- // src/measurement-draft-actions.ts
8766
- import { z } from "zod";
8767
- var idempotencyKeySchema = z.string().trim().min(1).describe(
8768
- "A fresh request key. Reuse it only when retrying the identical request."
8769
- );
8770
- var draftEtagSchema = z.string().trim().min(1).optional().describe(
8771
- "Current draft ETag from canonry_measurement_draft_get. The API requires it for draft edits, publish, and discard; omit it only to receive the API\u2019s actionable 428 response."
8772
- );
8773
- function mutationOperationSchema(action, request) {
8774
- return z.object({
8775
- action: z.literal(action),
8776
- request,
8777
- etag: draftEtagSchema,
8778
- idempotencyKey: idempotencyKeySchema
8779
- }).strict().describe(`Operation for ${action}.`);
8780
- }
8781
- var measurementDraftOperationSchema = z.discriminatedUnion("action", [
8782
- z.object({
8783
- action: z.literal("create"),
8784
- request: measurementDraftCreateRequestSchema,
8785
- idempotencyKey: idempotencyKeySchema
8786
- }).strict().describe("Operation for create."),
8787
- mutationOperationSchema("import-sitemap", measurementDraftImportSitemapRequestSchema),
8788
- mutationOperationSchema("apply-sitemap-selection", measurementDraftApplySitemapSelectionRequestSchema),
8789
- mutationOperationSchema("upsert-target", measurementDraftUpsertTargetRequestSchema),
8790
- mutationOperationSchema("rename-target", measurementDraftRenameTargetRequestSchema),
8791
- mutationOperationSchema("merge-targets", measurementDraftMergeTargetsRequestSchema),
8792
- mutationOperationSchema("exclude-target", measurementDraftExcludeTargetRequestSchema),
8793
- mutationOperationSchema("rebind-target", measurementDraftRebindTargetRequestSchema),
8794
- mutationOperationSchema("apply-assignments", measurementDraftApplyAssignmentsRequestSchema),
8795
- z.object({
8796
- action: z.literal("preview-assignments"),
8797
- request: measurementDraftPreviewAssignmentsRequestSchema
8798
- }).strict().describe("Read-semantic assignment impact preview."),
8799
- mutationOperationSchema("replace-assignments", measurementDraftReplaceAssignmentsRequestSchema),
8800
- mutationOperationSchema("apply-paired-assignments", measurementDraftApplyPairedAssignmentsRequestSchema),
8801
- mutationOperationSchema("remove-assignment", measurementDraftRemoveAssignmentRequestSchema),
8802
- mutationOperationSchema("clear-assignments", measurementDraftClearAssignmentsRequestSchema),
8803
- mutationOperationSchema("classify-assignments", measurementDraftClassifyAssignmentsRequestSchema),
8804
- mutationOperationSchema("upsert-group", measurementDraftUpsertGroupRequestSchema),
8805
- mutationOperationSchema("remove-group", measurementDraftRemoveGroupRequestSchema),
8806
- z.object({
8807
- action: z.literal("preview-group-membership"),
8808
- request: measurementDraftPreviewGroupMembershipRequestSchema
8809
- }).strict().describe("Read-semantic CSV group-membership preview."),
8810
- mutationOperationSchema("apply-group-membership", measurementDraftApplyGroupMembershipRequestSchema),
8811
- mutationOperationSchema("upsert-competitor", measurementDraftUpsertCompetitorRequestSchema),
8812
- mutationOperationSchema("remove-competitor", measurementDraftRemoveCompetitorRequestSchema),
8813
- z.object({ action: z.literal("compile-preview") }).strict().describe("Operation for compile-preview."),
8814
- z.object({ action: z.literal("diff-preview") }).strict().describe("Operation for diff-preview."),
8815
- mutationOperationSchema("publish", measurementDraftPublishRequestSchema),
8816
- z.object({
8817
- action: z.literal("discard"),
8818
- etag: draftEtagSchema,
8819
- idempotencyKey: idempotencyKeySchema
8820
- }).strict().describe("Operation for discard.")
8821
- ]);
8822
- function runMeasurementDraftAction(client2, project, actionInput) {
8823
- switch (actionInput.action) {
8824
- case "create":
8825
- return client2.createMeasurementPlanDraft(project, actionInput.request, actionInput.idempotencyKey);
8826
- case "import-sitemap":
8827
- return client2.importMeasurementDraftSitemap(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8828
- case "apply-sitemap-selection":
8829
- return client2.applyMeasurementDraftSitemapSelection(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8830
- case "upsert-target":
8831
- return client2.upsertMeasurementDraftTarget(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8832
- case "rename-target":
8833
- return client2.renameMeasurementDraftTarget(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8834
- case "merge-targets":
8835
- return client2.mergeMeasurementDraftTargets(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8836
- case "exclude-target":
8837
- return client2.excludeMeasurementDraftTarget(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8838
- case "rebind-target":
8839
- return client2.rebindMeasurementDraftTarget(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8840
- case "apply-assignments":
8841
- return client2.applyMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8842
- case "preview-assignments":
8843
- return client2.previewMeasurementDraftAssignments(project, actionInput.request);
8844
- case "replace-assignments":
8845
- return client2.replaceMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8846
- case "apply-paired-assignments":
8847
- return client2.applyPairedMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8848
- case "remove-assignment":
8849
- return client2.removeMeasurementDraftAssignment(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8850
- case "clear-assignments":
8851
- return client2.clearMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8852
- case "classify-assignments":
8853
- return client2.classifyMeasurementDraftAssignments(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8854
- case "upsert-group":
8855
- return client2.upsertMeasurementDraftGroup(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8856
- case "remove-group":
8857
- return client2.removeMeasurementDraftGroup(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8858
- case "preview-group-membership":
8859
- return client2.previewMeasurementDraftGroupMembership(project, actionInput.request);
8860
- case "apply-group-membership":
8861
- return client2.applyMeasurementDraftGroupMembership(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8862
- case "upsert-competitor":
8863
- return client2.upsertMeasurementDraftCompetitor(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8864
- case "remove-competitor":
8865
- return client2.removeMeasurementDraftCompetitor(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8866
- case "compile-preview":
8867
- return client2.compileMeasurementDraftPreview(project);
8868
- case "diff-preview":
8869
- return client2.diffMeasurementDraftPreview(project);
8870
- case "publish":
8871
- return client2.publishMeasurementDraft(project, actionInput.request, actionInput.idempotencyKey, actionInput.etag);
8872
- case "discard":
8873
- return client2.discardMeasurementDraft(project, actionInput.idempotencyKey, actionInput.etag);
8874
- }
8875
- }
8876
-
8877
- // src/mcp/schema.ts
8878
- import { z as z2 } from "zod";
8879
- var projectNameSchema = z2.string().min(1).describe("Canonry project name.");
8880
- var runIdSchema = z2.string().min(1).describe("Canonry run ID.");
8881
- var insightIdSchema = z2.string().min(1).describe("Canonry insight ID.");
8882
- var analyticsWindowSchema = z2.enum(["7d", "30d", "90d", "all"]).describe("Analytics time window.");
8883
- var emptyInputSchema = z2.object({});
8884
- var projectInputSchema = z2.object({
8885
- project: projectNameSchema
8889
+ var gaWindowInputSchema = z3.object({
8890
+ project: projectNameSchema,
8891
+ window: analyticsWindowSchema.optional(),
8892
+ startDate: z3.string().optional(),
8893
+ endDate: z3.string().optional()
8886
8894
  });
8887
- function toJsonSchema(schema, name) {
8888
- return {
8889
- ...z2.toJSONSchema(schema, { target: "draft-7" }),
8890
- title: name
8891
- };
8892
- }
8893
- function compactStringParams(values, keys) {
8894
- const params = {};
8895
- for (const key of keys) {
8896
- const value = values[key];
8897
- if (value === void 0 || value === null || value === "") continue;
8898
- if (typeof value === "string") {
8899
- params[key] = value;
8900
- } else if (typeof value === "number" || typeof value === "boolean") {
8901
- params[key] = String(value);
8902
- }
8903
- }
8904
- return Object.keys(params).length ? params : void 0;
8905
- }
8906
- function uniqueStrings(values) {
8907
- const seen = /* @__PURE__ */ new Set();
8908
- const result = [];
8909
- for (const value of values) {
8910
- const trimmed = value.trim();
8911
- if (!trimmed || seen.has(trimmed)) continue;
8912
- seen.add(trimmed);
8913
- result.push(trimmed);
8914
- }
8915
- return result;
8916
- }
8917
-
8918
- // src/mcp/tool-registry.ts
8919
- var readAnnotations = (openWorldHint) => ({
8920
- readOnlyHint: true,
8921
- ...openWorldHint ? { openWorldHint } : {}
8895
+ var GA_RANGE_PARAMS = ["window", "startDate", "endDate"];
8896
+ var gaTrafficInputSchema = gaWindowInputSchema.extend({
8897
+ limit: z3.number().int().positive().max(500).optional()
8922
8898
  });
8923
- var writeAnnotations = (opts) => ({
8924
- readOnlyHint: false,
8925
- idempotentHint: opts.idempotentHint,
8926
- destructiveHint: Boolean(opts.destructiveHint),
8927
- ...opts.openWorldHint ? { openWorldHint: opts.openWorldHint } : {}
8899
+ var gaMeasurementAnalysisInputSchema = z3.object({
8900
+ project: projectNameSchema,
8901
+ window: gaMeasurementAnalysisWindowSchema.optional(),
8902
+ hostScope: gaMeasurementHostScopeSchema.optional(),
8903
+ pathPrefix: z3.string().min(1).optional(),
8904
+ limit: z3.number().int().positive().max(100).optional()
8928
8905
  });
8929
- function defineTool(tool) {
8930
- return {
8931
- ...tool,
8932
- inputJsonSchema: toJsonSchema(tool.inputSchema, tool.name)
8933
- };
8934
- }
8935
- var runTriggerInputSchema = z3.object({
8906
+ var queriesInputSchema = z3.object({
8936
8907
  project: projectNameSchema,
8937
- request: runTriggerRequestSchema.optional()
8908
+ request: queryBatchRequestSchema
8938
8909
  });
8939
- var measurementPlanVersionInputSchema = z3.object({ project: projectNameSchema, revision: z3.number().int().positive() });
8940
- var measurementReportInputSchema = measurementPlanVersionInputSchema.extend({
8941
- runId: runIdSchema.optional().describe("Exact eligible full measurement run to reconstruct. Omit for the latest run in the revision.")
8910
+ var queryGenerateInputSchema = z3.object({
8911
+ project: projectNameSchema,
8912
+ request: queryGenerateRequestSchema
8942
8913
  });
8943
- var measurementPlanPreviewInputSchema = z3.object({ project: projectNameSchema, plan: measurementPlanAuthoringSchema });
8944
- var measurementPlanPublishInputSchema = measurementPlanPublishRequestSchema.extend({ project: projectNameSchema });
8945
- var measurementPlanRetireInputSchema = z3.object({ project: projectNameSchema, stableKey: z3.string().min(1) });
8946
- var measurementDiscoveryInputSchema = measurementDiscoveryRequestSchema.extend({ project: projectNameSchema });
8947
- var idempotencyKeyInputSchema = z3.string().trim().min(1).describe("A fresh request key. Reuse it only when retrying the identical request.");
8948
- var measurementOverviewInputSchema = z3.object({
8914
+ var gbpListLocationsInputSchema = z3.object({
8949
8915
  project: projectNameSchema,
8950
- scope: measurementOverviewQuerySchema.shape.scope.describe("Read all Properties, one reporting group, or one Property."),
8951
- groupKey: measurementOverviewQuerySchema.shape.groupKey.describe("Group stable key. Required only for group scope."),
8952
- targetKey: measurementOverviewQuerySchema.shape.targetKey.describe("Property stable key. Required only for property scope."),
8953
- queryClass: measurementOverviewQuerySchema.shape.queryClass,
8954
- provider: measurementOverviewQuerySchema.shape.provider,
8955
- location: measurementOverviewQuerySchema.shape.location,
8956
- from: measurementOverviewQuerySchema.shape.from,
8957
- to: measurementOverviewQuerySchema.shape.to,
8958
- runId: measurementOverviewQuerySchema.shape.runId,
8959
- search: measurementOverviewQuerySchema.shape.search,
8960
- sort: measurementOverviewQuerySchema.shape.sort,
8961
- cursor: measurementOverviewQuerySchema.shape.cursor,
8962
- limit: measurementOverviewQuerySchema.shape.limit
8963
- }).strict().superRefine((input, context) => {
8964
- if (input.scope === "group" && !input.groupKey) {
8965
- context.addIssue({ code: "custom", path: ["groupKey"], message: "Group scope requires groupKey." });
8966
- }
8967
- if (input.scope === "property" && !input.targetKey) {
8968
- context.addIssue({ code: "custom", path: ["targetKey"], message: "Property scope requires targetKey." });
8969
- }
8970
- if (input.scope !== "group" && input.groupKey) {
8971
- context.addIssue({ code: "custom", path: ["groupKey"], message: `${input.scope} scope does not accept groupKey.` });
8972
- }
8973
- if (input.scope !== "property" && input.targetKey) {
8974
- context.addIssue({ code: "custom", path: ["targetKey"], message: `${input.scope} scope does not accept targetKey.` });
8975
- }
8916
+ selected: z3.boolean().optional()
8976
8917
  });
8977
- var measurementPropertyEvidenceInputSchema = measurementPropertyEvidenceQuerySchema.extend({
8918
+ var gbpDiscoverInputSchema = z3.object({
8978
8919
  project: projectNameSchema,
8979
- targetKey: measurementPropertyEvidenceQuerySchema.shape.targetKey.describe("Property stable key. Required \u2014 this read is scoped to exactly one Property."),
8980
- shape: measurementPropertyEvidenceQuerySchema.shape.shape.describe(
8981
- "What one row is. Omit for sources (one row per cited URL). answers gives one row per measured answer with its cited URLs nested, including the answers that cited nothing."
8982
- )
8983
- }).strict();
8984
- var measurementPortfolioSummaryInputSchema = measurementPortfolioSummaryQuerySchema.extend({
8985
- project: projectNameSchema
8986
- }).strict();
8987
- var measurementPropertyQuestionsInputSchema = measurementPropertyQuestionsQuerySchema.extend({
8988
- project: projectNameSchema
8989
- }).strict();
8990
- var measurementQuestionResultInputSchema = measurementQuestionResultQuerySchema.extend({
8991
- project: projectNameSchema
8992
- }).strict();
8993
- var measurementPropertyCompetitorsInputSchema = measurementPropertyCompetitorsQuerySchema.extend({
8994
- project: projectNameSchema
8995
- }).strict();
8996
- var measurementChangesInputSchema = measurementChangesQuerySchema.extend({
8997
- project: projectNameSchema
8998
- }).strict();
8999
- var measurementDataQualityInputSchema = measurementDataQualityQuerySchema.extend({
9000
- project: projectNameSchema
9001
- }).strict();
9002
- var measurementDraftCollectionInputSchema = measurementDraftCollectionQuerySchema.extend({ project: projectNameSchema });
9003
- var measurementQuerySetInputSchema = z3.object({
8920
+ selectAllNew: z3.boolean().optional().default(true),
8921
+ accountName: z3.string().regex(/^accounts\//, 'accountName must be a Google resource name like "accounts/12345"').optional(),
8922
+ switchAccount: z3.boolean().optional().default(false)
8923
+ });
8924
+ var gbpLocationSelectionInputSchema = z3.object({
9004
8925
  project: projectNameSchema,
9005
- setId: z3.string().trim().min(1)
9006
- }).strict();
9007
- var measurementQuerySetUpsertInputSchema = measurementQuerySetInputSchema.extend({
9008
- request: measurementQuerySetUpsertRequestSchema
9009
- }).strict();
9010
- var measurementQueryTemplateInputSchema = z3.object({
8926
+ locationName: z3.string().min(1).regex(/^locations\//, 'locationName must be a Google resource name like "locations/12345"'),
8927
+ selected: z3.boolean()
8928
+ });
8929
+ var gbpSyncInputSchema = z3.object({
9011
8930
  project: projectNameSchema,
9012
- templateId: z3.string().trim().min(1)
9013
- }).strict();
9014
- var measurementQueryTemplateUpsertInputSchema = measurementQueryTemplateInputSchema.extend({
9015
- request: measurementQueryTemplateUpsertRequestSchema
9016
- }).strict();
9017
- var measurementQueryTemplateApplyInputSchema = measurementQueryTemplateInputSchema.extend({
9018
- request: measurementQueryTemplateApplyRequestSchema,
9019
- idempotencyKey: idempotencyKeyInputSchema
9020
- }).strict();
9021
- var measurementPlanDeactivateInputSchema = measurementPlanDeactivateRequestSchema.extend({
8931
+ locationNames: z3.array(z3.string()).optional(),
8932
+ daysOfMetrics: z3.number().int().positive().max(540).optional(),
8933
+ monthsOfKeywords: z3.number().int().positive().max(18).optional()
8934
+ });
8935
+ var gbpMetricsInputSchema = z3.object({
9022
8936
  project: projectNameSchema,
9023
- idempotencyKey: idempotencyKeyInputSchema
9024
- }).strict();
9025
- var measurementDraftActionInputSchema = z3.object({
8937
+ locationName: z3.string().optional(),
8938
+ metric: z3.string().optional()
8939
+ });
8940
+ var gbpLocationScopedInputSchema = z3.object({
9026
8941
  project: projectNameSchema,
9027
- operation: measurementDraftOperationSchema.describe("Typed draft operation. Select exactly one action branch.")
9028
- }).strict();
9029
- var measurementDraftActionOpenApiOperations = [
9030
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/create",
9031
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/import-sitemap",
9032
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-sitemap-selection",
9033
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-target",
9034
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/rename-target",
9035
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/merge-targets",
9036
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/exclude-target",
9037
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/rebind-target",
9038
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-assignments",
9039
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/preview-assignments",
9040
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/replace-assignments",
9041
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-paired-assignments",
9042
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-assignment",
9043
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/clear-assignments",
9044
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/classify-assignments",
9045
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-group",
9046
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-group",
9047
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/preview-group-membership",
9048
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/apply-group-membership",
9049
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/upsert-competitor",
9050
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/remove-competitor",
9051
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/compile-preview",
9052
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/diff-preview",
9053
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/publish",
9054
- "POST /api/v1/projects/{name}/measurement-plan/draft/actions/discard"
9055
- ];
9056
- var runsListInputSchema = z3.object({
8942
+ locationName: z3.string().optional()
8943
+ });
8944
+ var gbpAccountsInputSchema = z3.object({
8945
+ project: projectNameSchema
8946
+ });
8947
+ var adsInsightsInputSchema = z3.object({
9057
8948
  project: projectNameSchema,
9058
- limit: z3.number().int().positive().max(500).optional()
8949
+ level: z3.enum(["campaign", "ad_group"]).optional(),
8950
+ entityId: z3.string().optional(),
8951
+ from: z3.string().optional(),
8952
+ to: z3.string().optional()
9059
8953
  });
9060
- var runGetInputSchema = z3.object({
9061
- runId: runIdSchema
8954
+ var adsGeoSearchInputSchema = adsGeoSearchQuerySchema.extend({
8955
+ project: projectNameSchema
9062
8956
  });
9063
- var timelineInputSchema = z3.object({
8957
+ var adsLiveDeliveryInputSchema = z3.object({
9064
8958
  project: projectNameSchema,
9065
- location: z3.string().optional().describe("Location label. Use an empty string for locationless results."),
9066
- limit: z3.number().int().positive().max(100).optional().describe("Restrict history to the most recent N project runs.")
8959
+ campaignId: z3.string().min(1).max(200).optional(),
8960
+ lookbackDays: z3.number().int().min(1).max(30).optional()
9067
8961
  });
9068
- var historyFilterShape = {
9069
- limit: z3.number().int().positive().max(500).optional(),
9070
- offset: z3.number().int().nonnegative().optional(),
9071
- since: z3.string().optional().describe("ISO 8601 lower bound."),
9072
- action: z3.string().optional().describe("Exact audit action filter."),
9073
- actor: z3.string().optional().describe("Exact actor filter."),
9074
- entityType: z3.string().optional().describe("Exact entity type filter.")
9075
- };
9076
- var projectHistoryInputSchema = z3.object({ project: projectNameSchema, ...historyFilterShape });
9077
- var globalHistoryInputSchema = z3.object(historyFilterShape);
9078
- var snapshotsListInputSchema = z3.object({
8962
+ var adsOperationInputSchema = z3.object({
9079
8963
  project: projectNameSchema,
9080
- limit: z3.number().int().positive().max(500).optional(),
9081
- offset: z3.number().int().nonnegative().optional(),
9082
- location: z3.string().optional().describe("Location label. Use an empty string for locationless results.")
8964
+ operationKey: z3.string().min(8).max(128)
9083
8965
  });
9084
- var snapshotsDiffInputSchema = z3.object({
8966
+ var adsOperationResumeActivationInputSchema = adsOperationInputSchema.strict();
8967
+ var adsUnresolvedOperationsInputSchema = adsUnresolvedOperationListQuerySchema.extend({
8968
+ project: projectNameSchema
8969
+ });
8970
+ var adsOperationReconcileInputSchema = adsOperationReconcileRequestSchema.extend({
9085
8971
  project: projectNameSchema,
9086
- run1: runIdSchema,
9087
- run2: runIdSchema
8972
+ operationKey: z3.string().min(8).max(128)
9088
8973
  });
9089
- var insightsListInputSchema = z3.object({
8974
+ var adsImageUploadInputSchema = z3.object({
9090
8975
  project: projectNameSchema,
9091
- dismissed: z3.boolean().optional(),
9092
- runId: runIdSchema.optional()
8976
+ request: adsImageUploadRequestSchema
9093
8977
  });
9094
- var insightInputSchema = z3.object({
8978
+ var adsCampaignCreateInputSchema = z3.object({
9095
8979
  project: projectNameSchema,
9096
- insightId: insightIdSchema
8980
+ request: adsCampaignCreateRequestSchema
9097
8981
  });
9098
- var healthHistoryInputSchema = z3.object({
8982
+ var adsCampaignUpdateInputSchema = z3.object({
9099
8983
  project: projectNameSchema,
9100
- limit: z3.number().int().positive().max(100).optional()
8984
+ campaignId: z3.string().min(1),
8985
+ request: adsCampaignUpdateRequestSchema
9101
8986
  });
9102
- var gscPerformanceInputSchema = z3.object({
8987
+ var adsCampaignActivateTreeInputSchema = z3.object({
9103
8988
  project: projectNameSchema,
9104
- startDate: z3.string().optional(),
9105
- endDate: z3.string().optional(),
9106
- query: z3.string().optional(),
9107
- page: z3.string().optional(),
9108
- limit: z3.number().int().positive().max(5e3).optional(),
9109
- offset: z3.number().int().nonnegative().optional(),
9110
- orderBy: gscPerformanceOrderBySchema.optional(),
9111
- window: analyticsWindowSchema.optional()
8989
+ campaignId: z3.string().min(1),
8990
+ request: adsActivateTreeRequestSchema
9112
8991
  });
9113
- var gscPerformanceDailyInputSchema = z3.object({
8992
+ var adsCampaignPauseInputSchema = z3.object({
9114
8993
  project: projectNameSchema,
9115
- startDate: z3.string().optional(),
9116
- endDate: z3.string().optional(),
9117
- window: analyticsWindowSchema.optional()
8994
+ campaignId: z3.string().min(1),
8995
+ request: adsPauseRequestSchema
9118
8996
  });
9119
- var gscTopPagesInputSchema = z3.object({
8997
+ var adsAdGroupCreateInputSchema = z3.object({
9120
8998
  project: projectNameSchema,
9121
- startDate: z3.string().optional(),
9122
- endDate: z3.string().optional(),
9123
- limit: z3.number().int().positive().max(500).optional(),
9124
- window: analyticsWindowSchema.optional()
8999
+ request: adsAdGroupCreateRequestSchema
9125
9000
  });
9126
- var gscInspectionsInputSchema = z3.object({
9001
+ var adsAdGroupUpdateInputSchema = z3.object({
9127
9002
  project: projectNameSchema,
9128
- url: z3.string().optional(),
9129
- limit: z3.number().int().positive().max(500).optional()
9003
+ adGroupId: z3.string().min(1),
9004
+ request: adsAdGroupUpdateRequestSchema
9130
9005
  });
9131
- var gscCoverageHistoryInputSchema = z3.object({
9006
+ var adsAdGroupPauseInputSchema = z3.object({
9132
9007
  project: projectNameSchema,
9133
- limit: z3.number().int().positive().max(500).optional()
9008
+ adGroupId: z3.string().min(1),
9009
+ request: adsPauseRequestSchema
9134
9010
  });
9135
- var gscSitemapsInputSchema = z3.object({
9011
+ var adsAdCreateInputSchema = z3.object({
9136
9012
  project: projectNameSchema,
9137
- sitemapIndex: z3.string().url().optional()
9138
- });
9139
- var gscSitemapsSubmitInputSchema = z3.union([
9140
- z3.object({
9141
- project: projectNameSchema,
9142
- sitemapUrls: z3.array(z3.string().url()).min(1).max(50)
9143
- }).strict(),
9144
- z3.object({
9145
- project: projectNameSchema,
9146
- mode: z3.enum(["indexes", "all-files"])
9147
- }).strict()
9148
- ]);
9149
- async function submitGscSitemapsFromMcp(client2, input) {
9150
- let sitemapUrls;
9151
- if ("sitemapUrls" in input) {
9152
- sitemapUrls = uniqueStrings(input.sitemapUrls);
9153
- } else {
9154
- const topLevel = await client2.gscSitemaps(input.project);
9155
- if (input.mode === "indexes") {
9156
- sitemapUrls = uniqueStrings(
9157
- topLevel.preferredSubmissionUrls.length > 0 ? topLevel.preferredSubmissionUrls : topLevel.sitemaps.map((sitemap) => sitemap.path)
9158
- );
9159
- } else {
9160
- const indexes = topLevel.sitemaps.filter((sitemap) => sitemap.isSitemapsIndex).map((sitemap) => sitemap.path);
9161
- const expandedIndexUrls = [];
9162
- for (let offset = 0; offset < indexes.length; offset += 4) {
9163
- const children = await Promise.all(
9164
- indexes.slice(offset, offset + 4).map(
9165
- (sitemapIndex) => client2.gscSitemaps(input.project, { sitemapIndex })
9166
- )
9167
- );
9168
- children.forEach((result, index) => {
9169
- const sitemapIndex = indexes[offset + index];
9170
- expandedIndexUrls.push(...result.sitemaps.length > 0 ? result.sitemaps.map((sitemap) => sitemap.path) : [sitemapIndex]);
9171
- });
9172
- }
9173
- sitemapUrls = uniqueStrings([
9174
- ...topLevel.sitemaps.filter((sitemap) => !sitemap.isSitemapsIndex).map((sitemap) => sitemap.path),
9175
- ...expandedIndexUrls
9176
- ]);
9177
- }
9178
- }
9179
- if (sitemapUrls.length === 0) {
9180
- throw new Error("No GSC sitemaps found. Submit an explicit sitemap URL first.");
9181
- }
9182
- const aggregate = {
9183
- summary: { total: 0, accepted: 0, failed: 0 },
9184
- results: []
9185
- };
9186
- for (let offset = 0; offset < sitemapUrls.length; offset += 50) {
9187
- const batchSitemapUrls = sitemapUrls.slice(offset, offset + 50);
9188
- try {
9189
- const result = await client2.gscSubmitSitemaps(input.project, {
9190
- sitemapUrls: batchSitemapUrls
9191
- });
9192
- aggregate.summary.total += result.summary.total;
9193
- aggregate.summary.accepted += result.summary.accepted;
9194
- aggregate.summary.failed += result.summary.failed;
9195
- aggregate.results.push(...result.results);
9196
- } catch (cause) {
9197
- if (offset === 0) throw cause;
9198
- const attempted = aggregate.summary.total + batchSitemapUrls.length;
9199
- const remaining = sitemapUrls.length - attempted;
9200
- throw new CliError({
9201
- code: "GOOGLE_SITEMAP_SUBMISSION_PARTIAL",
9202
- message: `Sitemap submission stopped after ${aggregate.summary.total} completed results; ${batchSitemapUrls.length} are unconfirmed and ${remaining} were not attempted.`,
9203
- exitCode: cause instanceof CliError && cause.exitCode === 1 ? 1 : EXIT_SYSTEM_ERROR,
9204
- details: {
9205
- project: input.project,
9206
- accepted: aggregate.summary.accepted,
9207
- failed: aggregate.summary.failed,
9208
- completed: aggregate.summary.total,
9209
- attempted,
9210
- unconfirmed: batchSitemapUrls.length,
9211
- remaining,
9212
- unconfirmedBatch: { index: Math.floor(offset / 50) + 1, sitemapUrls: batchSitemapUrls },
9213
- partialResult: aggregate,
9214
- cause: cause instanceof CliError ? { code: cause.code, message: cause.message, details: cause.details } : { message: describeError(cause) }
9215
- }
9216
- });
9217
- }
9218
- }
9219
- return aggregate;
9220
- }
9221
- var gaWindowInputSchema = z3.object({
9222
- project: projectNameSchema,
9223
- window: analyticsWindowSchema.optional(),
9224
- startDate: z3.string().optional(),
9225
- endDate: z3.string().optional()
9226
- });
9227
- var GA_RANGE_PARAMS = ["window", "startDate", "endDate"];
9228
- var gaTrafficInputSchema = gaWindowInputSchema.extend({
9229
- limit: z3.number().int().positive().max(500).optional()
9230
- });
9231
- var gaMeasurementAnalysisInputSchema = z3.object({
9232
- project: projectNameSchema,
9233
- window: gaMeasurementAnalysisWindowSchema.optional(),
9234
- hostScope: gaMeasurementHostScopeSchema.optional(),
9235
- pathPrefix: z3.string().min(1).optional(),
9236
- limit: z3.number().int().positive().max(100).optional()
9237
- });
9238
- var queriesInputSchema = z3.object({
9239
- project: projectNameSchema,
9240
- request: queryBatchRequestSchema
9241
- });
9242
- var queryGenerateInputSchema = z3.object({
9243
- project: projectNameSchema,
9244
- request: queryGenerateRequestSchema
9245
- });
9246
- var gbpListLocationsInputSchema = z3.object({
9247
- project: projectNameSchema,
9248
- selected: z3.boolean().optional()
9249
- });
9250
- var gbpDiscoverInputSchema = z3.object({
9251
- project: projectNameSchema,
9252
- selectAllNew: z3.boolean().optional().default(true),
9253
- accountName: z3.string().regex(/^accounts\//, 'accountName must be a Google resource name like "accounts/12345"').optional(),
9254
- switchAccount: z3.boolean().optional().default(false)
9255
- });
9256
- var gbpLocationSelectionInputSchema = z3.object({
9257
- project: projectNameSchema,
9258
- locationName: z3.string().min(1).regex(/^locations\//, 'locationName must be a Google resource name like "locations/12345"'),
9259
- selected: z3.boolean()
9260
- });
9261
- var gbpSyncInputSchema = z3.object({
9262
- project: projectNameSchema,
9263
- locationNames: z3.array(z3.string()).optional(),
9264
- daysOfMetrics: z3.number().int().positive().max(540).optional(),
9265
- monthsOfKeywords: z3.number().int().positive().max(18).optional()
9266
- });
9267
- var gbpMetricsInputSchema = z3.object({
9268
- project: projectNameSchema,
9269
- locationName: z3.string().optional(),
9270
- metric: z3.string().optional()
9271
- });
9272
- var gbpLocationScopedInputSchema = z3.object({
9273
- project: projectNameSchema,
9274
- locationName: z3.string().optional()
9275
- });
9276
- var gbpAccountsInputSchema = z3.object({
9277
- project: projectNameSchema
9278
- });
9279
- var adsInsightsInputSchema = z3.object({
9280
- project: projectNameSchema,
9281
- level: z3.enum(["campaign", "ad_group"]).optional(),
9282
- entityId: z3.string().optional(),
9283
- from: z3.string().optional(),
9284
- to: z3.string().optional()
9285
- });
9286
- var adsGeoSearchInputSchema = adsGeoSearchQuerySchema.extend({
9287
- project: projectNameSchema
9288
- });
9289
- var adsLiveDeliveryInputSchema = z3.object({
9290
- project: projectNameSchema,
9291
- campaignId: z3.string().min(1).max(200).optional(),
9292
- lookbackDays: z3.number().int().min(1).max(30).optional()
9293
- });
9294
- var adsOperationInputSchema = z3.object({
9295
- project: projectNameSchema,
9296
- operationKey: z3.string().min(8).max(128)
9297
- });
9298
- var adsOperationResumeActivationInputSchema = adsOperationInputSchema.strict();
9299
- var adsUnresolvedOperationsInputSchema = adsUnresolvedOperationListQuerySchema.extend({
9300
- project: projectNameSchema
9301
- });
9302
- var adsOperationReconcileInputSchema = adsOperationReconcileRequestSchema.extend({
9303
- project: projectNameSchema,
9304
- operationKey: z3.string().min(8).max(128)
9305
- });
9306
- var adsImageUploadInputSchema = z3.object({
9307
- project: projectNameSchema,
9308
- request: adsImageUploadRequestSchema
9309
- });
9310
- var adsCampaignCreateInputSchema = z3.object({
9311
- project: projectNameSchema,
9312
- request: adsCampaignCreateRequestSchema
9313
- });
9314
- var adsCampaignUpdateInputSchema = z3.object({
9315
- project: projectNameSchema,
9316
- campaignId: z3.string().min(1),
9317
- request: adsCampaignUpdateRequestSchema
9318
- });
9319
- var adsCampaignActivateTreeInputSchema = z3.object({
9320
- project: projectNameSchema,
9321
- campaignId: z3.string().min(1),
9322
- request: adsActivateTreeRequestSchema
9323
- });
9324
- var adsCampaignPauseInputSchema = z3.object({
9325
- project: projectNameSchema,
9326
- campaignId: z3.string().min(1),
9327
- request: adsPauseRequestSchema
9328
- });
9329
- var adsAdGroupCreateInputSchema = z3.object({
9330
- project: projectNameSchema,
9331
- request: adsAdGroupCreateRequestSchema
9332
- });
9333
- var adsAdGroupUpdateInputSchema = z3.object({
9334
- project: projectNameSchema,
9335
- adGroupId: z3.string().min(1),
9336
- request: adsAdGroupUpdateRequestSchema
9337
- });
9338
- var adsAdGroupPauseInputSchema = z3.object({
9339
- project: projectNameSchema,
9340
- adGroupId: z3.string().min(1),
9341
- request: adsPauseRequestSchema
9342
- });
9343
- var adsAdCreateInputSchema = z3.object({
9344
- project: projectNameSchema,
9345
- request: adsAdCreateRequestSchema
9013
+ request: adsAdCreateRequestSchema
9346
9014
  });
9347
9015
  var adsAdUpdateInputSchema = z3.object({
9348
9016
  project: projectNameSchema,
@@ -9359,6 +9027,10 @@ var googleMarketingSnapshotPageInputSchema = z3.object({
9359
9027
  limit: z3.number().int().min(1).max(GOOGLE_MARKETING_STORED_SNAPSHOT_PAGE_MAX).optional(),
9360
9028
  cursor: z3.string().trim().min(1).optional()
9361
9029
  }).strict();
9030
+ var googleAdsPerformanceInputSchema = z3.object({
9031
+ project: projectNameSchema,
9032
+ window: googleAdsMetricsWindowSchema.optional()
9033
+ }).strict();
9362
9034
  var googleMarketingSnapshotInputSchema = z3.object({
9363
9035
  project: projectNameSchema,
9364
9036
  snapshotId: z3.string().trim().min(1)
@@ -11772,6 +11444,31 @@ var canonryMcpTools = [
11772
11444
  openApiOperations: ["GET /api/v1/projects/{name}/google-ads/customers"],
11773
11445
  handler: (client2, input) => client2.listGoogleAdsCustomers(input.project)
11774
11446
  }),
11447
+ defineTool({
11448
+ name: "canonry_conversion_tracking_options",
11449
+ title: "List selectable conversion actions and GTM tags",
11450
+ description: "List the Google Ads conversion actions and Tag Manager tags a conversion contract can point at, read from the newest stored snapshots. Stored read: quota-free, never calls a provider, never spends the advertiser budget. An empty list means the provider has not synced, not that no options exist.",
11451
+ access: "read",
11452
+ tier: "setup",
11453
+ inputSchema: projectInputSchema,
11454
+ annotations: readAnnotations(),
11455
+ openApiOperations: ["GET /api/v1/projects/{name}/conversion-tracking/options"],
11456
+ handler: (client2, input) => client2.getConversionTrackingOptions(input.project)
11457
+ }),
11458
+ defineTool({
11459
+ name: "canonry_google_ads_performance",
11460
+ title: "Read stored Google Ads performance",
11461
+ description: "Read impressions, clicks, cost (integer micros), conversions, the densified daily series, per-campaign totals, and the prior-equal-period comparison from the newest stored campaign-metrics snapshot. Stored read: it is quota-free, never calls Google, and never spends the advertiser budget. The capture day is partial and is excluded, so every window ends on the newest closed day.",
11462
+ access: "read",
11463
+ tier: "google-ads",
11464
+ inputSchema: googleAdsPerformanceInputSchema,
11465
+ annotations: readAnnotations(),
11466
+ openApiOperations: ["GET /api/v1/projects/{name}/google-ads/performance"],
11467
+ handler: (client2, input) => client2.getGoogleAdsPerformance(
11468
+ input.project,
11469
+ input.window ? { window: input.window } : {}
11470
+ )
11471
+ }),
11775
11472
  defineTool({
11776
11473
  name: "canonry_google_ads_snapshots",
11777
11474
  title: "List stored Google Ads evidence",
@@ -12229,6 +11926,756 @@ var CanonryMcpToolNames = Object.freeze(
12229
11926
  Object.fromEntries(canonryMcpTools.map((tool) => [tool.name, tool.name]))
12230
11927
  );
12231
11928
 
11929
+ // src/mcp/results.ts
11930
+ import { ZodError } from "zod";
11931
+ function jsonToolResult(value) {
11932
+ const result = value === void 0 ? { ok: true } : value;
11933
+ return {
11934
+ content: [
11935
+ {
11936
+ type: "text",
11937
+ text: JSON.stringify(result, null, 2)
11938
+ }
11939
+ ]
11940
+ };
11941
+ }
11942
+ function errorToolResult(error) {
11943
+ return {
11944
+ isError: true,
11945
+ content: [
11946
+ {
11947
+ type: "text",
11948
+ text: JSON.stringify(toCanonryErrorEnvelope(error), null, 2)
11949
+ }
11950
+ ]
11951
+ };
11952
+ }
11953
+ async function withToolErrors(handler) {
11954
+ try {
11955
+ return jsonToolResult(await handler());
11956
+ } catch (error) {
11957
+ return errorToolResult(error);
11958
+ }
11959
+ }
11960
+ function toCanonryErrorEnvelope(error) {
11961
+ if (error instanceof ZodError) {
11962
+ return {
11963
+ error: {
11964
+ code: "VALIDATION_ERROR",
11965
+ message: zodErrorMessage(error),
11966
+ details: { issues: error.issues.map(formatZodIssue) }
11967
+ }
11968
+ };
11969
+ }
11970
+ if (error instanceof CliError) {
11971
+ return {
11972
+ error: {
11973
+ code: error.code,
11974
+ message: error.message,
11975
+ ...error.details ? { details: error.details } : {}
11976
+ }
11977
+ };
11978
+ }
11979
+ if (hasErrorEnvelope(error)) {
11980
+ const { code, message, details } = error.error;
11981
+ return {
11982
+ error: {
11983
+ code: typeof code === "string" ? code : "API_ERROR",
11984
+ message: typeof message === "string" ? message : "Canonry API error",
11985
+ ...details !== void 0 ? { details } : {}
11986
+ }
11987
+ };
11988
+ }
11989
+ if (error instanceof Error) {
11990
+ return {
11991
+ error: {
11992
+ code: "MCP_TOOL_ERROR",
11993
+ message: error.message
11994
+ }
11995
+ };
11996
+ }
11997
+ return {
11998
+ error: {
11999
+ code: "MCP_TOOL_ERROR",
12000
+ message: "Unknown MCP tool error"
12001
+ }
12002
+ };
12003
+ }
12004
+ function hasErrorEnvelope(value) {
12005
+ if (!value || typeof value !== "object" || !("error" in value)) return false;
12006
+ const error = value.error;
12007
+ return Boolean(error && typeof error === "object");
12008
+ }
12009
+ function formatZodIssue(issue) {
12010
+ return { path: issue.path.map(String).join("."), message: issue.message };
12011
+ }
12012
+ function zodErrorMessage(error) {
12013
+ const first = error.issues[0];
12014
+ if (!first) return "Input validation failed";
12015
+ const path3 = first.path.map(String).join(".");
12016
+ return path3 ? `${path3}: ${first.message}` : first.message;
12017
+ }
12018
+
12019
+ // src/mcp/toolkits.ts
12020
+ var CANONRY_MCP_TOOLKIT_NAMES = [
12021
+ "monitoring",
12022
+ "setup",
12023
+ "gsc",
12024
+ "ga",
12025
+ "gbp",
12026
+ "ads",
12027
+ "google-ads",
12028
+ "gtm",
12029
+ "conversion-tracking",
12030
+ "traffic",
12031
+ "agent",
12032
+ "discovery"
12033
+ ];
12034
+ var CANONRY_MCP_TOOLKITS = [
12035
+ {
12036
+ name: "monitoring",
12037
+ title: "Measurement, runs, insights, health",
12038
+ description: "Inspect portfolio and Property measurement, question results, run history, intelligence insights, health timelines, and bounded Site Health reads (overview, page audit evidence, focused subgraphs, paths, and scan changes).",
12039
+ whenToLoad: "Load when finding weak Properties, reviewing answers and competitors, checking data quality, tying a Site Health page score to concrete findings, investigating site architecture, comparing scans, or diagnosing regressions."
12040
+ },
12041
+ {
12042
+ name: "setup",
12043
+ title: "Project setup and measurement",
12044
+ description: "Manage project configuration, Advanced Measurement drafts, query assets, competitors, and schedules.",
12045
+ whenToLoad: "Load when onboarding a project, editing its measurement plan, or managing tracked queries, competitors, or schedules."
12046
+ },
12047
+ {
12048
+ name: "gsc",
12049
+ title: "Google Search Console",
12050
+ description: "Read GSC performance, inspections, coverage, sitemaps, and deindexed URLs.",
12051
+ whenToLoad: "Load when you need indexing, coverage, or sitemap data from Google Search Console."
12052
+ },
12053
+ {
12054
+ name: "ga",
12055
+ title: "Google Analytics 4",
12056
+ description: "Read GA traffic, AI/social referral history, attribution trend, and session history.",
12057
+ whenToLoad: "Load when you need traffic, referral, or attribution data from Google Analytics 4."
12058
+ },
12059
+ {
12060
+ name: "gbp",
12061
+ title: "Google Business Profile",
12062
+ description: "Local AEO signals: discover GBP locations under a connected account and toggle which ones sync. Future phases will add reviews, keyword impressions, daily performance metrics, and hotel attributes.",
12063
+ whenToLoad: "Load when the project tracks local search visibility or has connected Google Business Profile."
12064
+ },
12065
+ {
12066
+ name: "ads",
12067
+ title: "OpenAI ads (ChatGPT ads)",
12068
+ description: "Live OpenAI ad-account review state, targetable geo IDs, conversion pixels/event settings, synced campaign structure, paid-performance rollups, and guarded lifecycle operations.",
12069
+ whenToLoad: "Load when planning, creating, reviewing, or measuring ChatGPT ads, including geo and conversion-readiness checks before launch."
12070
+ },
12071
+ {
12072
+ name: "google-ads",
12073
+ title: "Google Ads conversion evidence",
12074
+ description: "Read a connected Google Ads account, its conversion actions, effective campaign goals, and bounded stored snapshots. v1 only reads provider state; it never edits campaigns, conversion actions, or bidding goals.",
12075
+ whenToLoad: "Load when selecting a Google Ads customer, reviewing conversion-action and effective-goal evidence, or starting a bounded read-only sync."
12076
+ },
12077
+ {
12078
+ name: "gtm",
12079
+ title: "Google Tag Manager conversion evidence",
12080
+ description: "Read GTM accounts, containers, workspaces, and sanitized live/draft tag graphs used to verify a conversion contract. v1 never edits a workspace or publishes a container version.",
12081
+ whenToLoad: "Load when selecting a GTM container, reviewing a live or draft conversion-tag graph, or starting a bounded read-only sync."
12082
+ },
12083
+ {
12084
+ name: "conversion-tracking",
12085
+ title: "Cross-provider conversion integrity",
12086
+ description: "Read declared conversion contracts and assess them against stored Google Ads and GTM evidence without calling or mutating either provider.",
12087
+ whenToLoad: "Load when listing or inspecting declared conversion contracts, or when checking whether stored Google Ads and GTM evidence is consistent with one contract."
12088
+ },
12089
+ {
12090
+ name: "traffic",
12091
+ title: "Server-side traffic ingestion",
12092
+ description: "Connect Cloud Run traffic sources, trigger syncs, and read crawler / AI-referral hourly rollups straight from server logs (no GA dependency).",
12093
+ whenToLoad: "Load when you need server-log evidence of crawler hits or AI-referral sessions (e.g. confirming GPTBot or ChatGPT-User on a page), or when wiring up / syncing a Cloud Run traffic source."
12094
+ },
12095
+ {
12096
+ name: "agent",
12097
+ title: "Aero agent lifecycle and memory",
12098
+ description: "Manage the built-in Aero agent: durable project-scoped memory (list/set/forget), clear the rolling transcript, and detach the external-agent webhook.",
12099
+ whenToLoad: "Load when reading or writing project-scoped Aero notes, clearing a stuck conversation, or removing an external agent webhook."
12100
+ },
12101
+ {
12102
+ name: "discovery",
12103
+ title: "Query discovery and research",
12104
+ description: "Two linked workflows: find queries expands an ICP into a promotable tracked-query basket; research queries runs saved free-form experiments against one API model. Research never adds queries to tracking.",
12105
+ whenToLoad: "Load when the operator wants to find ICP-led candidates for a tracked basket, or research specific free-form queries, model answers, sources, and location effects without changing tracking."
12106
+ }
12107
+ ];
12108
+ function isCanonryMcpToolkitName(value) {
12109
+ return CANONRY_MCP_TOOLKIT_NAMES.includes(value);
12110
+ }
12111
+
12112
+ // src/mcp/dynamic-catalog.ts
12113
+ var DynamicToolCatalog = class {
12114
+ entries;
12115
+ loaded = /* @__PURE__ */ new Set();
12116
+ eager;
12117
+ scope;
12118
+ server;
12119
+ constructor(server, entries, scope, options = {}) {
12120
+ this.server = server;
12121
+ this.entries = entries;
12122
+ this.scope = scope;
12123
+ this.eager = Boolean(options.eager);
12124
+ if (this.eager) {
12125
+ for (const toolkit of CANONRY_MCP_TOOLKITS) {
12126
+ if (this.toolsForToolkit(toolkit.name).length > 0) {
12127
+ this.loaded.add(toolkit.name);
12128
+ }
12129
+ }
12130
+ }
12131
+ }
12132
+ applyInitialEnablement() {
12133
+ if (this.eager) return;
12134
+ this.batchListChanged(() => {
12135
+ for (const entry of this.entries) {
12136
+ if (entry.tool.tier !== "core") entry.registered.disable();
12137
+ }
12138
+ });
12139
+ }
12140
+ loadToolkit(rawName) {
12141
+ if (!isCanonryMcpToolkitName(rawName)) {
12142
+ const valid = CANONRY_MCP_TOOLKITS.map((t) => t.name).join(", ");
12143
+ throw new Error(`Unknown toolkit "${rawName}". Available: ${valid}.`);
12144
+ }
12145
+ const name = rawName;
12146
+ const matches = this.entries.filter((entry) => entry.tool.tier === name);
12147
+ if (matches.length === 0) {
12148
+ return { status: "empty", name, tools: [] };
12149
+ }
12150
+ if (this.loaded.has(name)) {
12151
+ return { status: "already-loaded", name, tools: matches.map((entry) => entry.tool.name) };
12152
+ }
12153
+ this.batchListChanged(() => {
12154
+ for (const entry of matches) {
12155
+ entry.registered.enable();
12156
+ }
12157
+ });
12158
+ this.loaded.add(name);
12159
+ return { status: "loaded", name, tools: matches.map((entry) => entry.tool.name) };
12160
+ }
12161
+ helpResult() {
12162
+ return {
12163
+ scope: this.scope,
12164
+ eager: this.eager,
12165
+ loadedToolkits: [...this.loaded].sort(),
12166
+ coreTools: this.entries.filter((entry) => entry.tool.tier === "core").map((entry) => entry.tool.name),
12167
+ toolkits: CANONRY_MCP_TOOLKITS.map((toolkit) => this.toolkitEntry(toolkit)).filter((entry) => entry.toolCount > 0),
12168
+ usage: "Call canonry_load_toolkit with one of the toolkit names listed in `toolkits[].name` to register its tools for the rest of this session. Wait for its response before calling any newly enabled tool."
12169
+ };
12170
+ }
12171
+ toolkitEntry(toolkit) {
12172
+ const tools = this.toolsForToolkit(toolkit.name);
12173
+ return {
12174
+ name: toolkit.name,
12175
+ title: toolkit.title,
12176
+ description: toolkit.description,
12177
+ whenToLoad: toolkit.whenToLoad,
12178
+ toolCount: tools.length,
12179
+ tools,
12180
+ loaded: this.loaded.has(toolkit.name)
12181
+ };
12182
+ }
12183
+ toolsForToolkit(name) {
12184
+ return this.entries.filter((entry) => entry.tool.tier === name).map((entry) => entry.tool.name);
12185
+ }
12186
+ // RegisteredTool.enable/disable each call sendToolListChanged on the McpServer
12187
+ // we registered with. Loading an 11-tool toolkit emits 11 notifications under
12188
+ // that contract, which a spec-compliant client will treat as 11 catalog
12189
+ // refetches. Coalesce them into one notification per batch by intercepting
12190
+ // the SDK's sender for the duration of the batch.
12191
+ batchListChanged(fn) {
12192
+ const host = this.server;
12193
+ const original = host.sendToolListChanged.bind(host);
12194
+ let suppressed = false;
12195
+ host.sendToolListChanged = () => {
12196
+ suppressed = true;
12197
+ };
12198
+ try {
12199
+ fn();
12200
+ } finally {
12201
+ host.sendToolListChanged = original;
12202
+ }
12203
+ if (suppressed) original();
12204
+ }
12205
+ };
12206
+
12207
+ // src/mcp/server.ts
12208
+ function createCanonryMcpServer(options = {}) {
12209
+ return createCanonryMcpServerWithCatalog(options).server;
12210
+ }
12211
+ var SERVER_INSTRUCTIONS = `Canonry tracks how AI answer engines mention a brand and cite a domain.
12212
+
12213
+ Load the "canonry" skill before operator work (project setup, integrations, traffic sources, sweeps, diagnosis). It carries the procedures and the failure modes; this text is only a pointer. Use "aero" for analyst work: regression diagnosis, reporting.
12214
+
12215
+ Two signals, never interchangeable:
12216
+ - mentioned = the brand appears in the answer TEXT the model wrote.
12217
+ - cited = the domain appears in the SOURCE links behind the answer.
12218
+ A model can do either, both or neither. Never compute one from the other, and never report a number for one under the other's name.
12219
+
12220
+ Sweeps and probes spend provider quota and write rows. Get explicit approval before any run, apply, or other mutation.
12221
+
12222
+ Most reads are free. Five ads reads are NOT: canonry_ads_account, canonry_ads_geo_search, canonry_ads_live_delivery, canonry_ads_conversion_pixels and canonry_ads_conversion_event_settings call the provider live and spend against the advertiser account. They are marked read, so nothing in the tool list warns you. Get approval for those exactly as for a mutation.
12223
+
12224
+ Google Marketing also calls providers live. Get approval before canonry_google_ads_customers, canonry_gtm_accounts, canonry_gtm_containers, canonry_gtm_workspaces, canonry_google_ads_sync or canonry_gtm_sync.
12225
+
12226
+ A null answerMentioned means NOT CHECKED, not "not mentioned". Never coerce it to false.
12227
+
12228
+ If no sweep has run, say so. Never state a mention or citation figure that no run produced.`;
12229
+ function createCanonryMcpServerWithCatalog(options = {}) {
12230
+ const clientFactory = options.clientFactory ?? createApiClient;
12231
+ const client2 = clientFactory();
12232
+ const scope = options.scope ?? "all";
12233
+ const server = new McpServer({
12234
+ name: "canonry",
12235
+ version: PACKAGE_VERSION
12236
+ }, {
12237
+ instructions: SERVER_INSTRUCTIONS
12238
+ });
12239
+ server.validateToolInput = async (_tool, args) => args;
12240
+ const entries = [];
12241
+ for (const registryTool of getCanonryMcpTools(scope, options.tiers)) {
12242
+ const tool = registryTool;
12243
+ const handler = tool.handler;
12244
+ const registered = server.registerTool(
12245
+ tool.name,
12246
+ {
12247
+ title: tool.title,
12248
+ description: tool.description,
12249
+ inputSchema: tool.inputSchema,
12250
+ annotations: tool.annotations
12251
+ },
12252
+ async (input) => withToolErrors(async () => {
12253
+ const parsed = tool.inputSchema.parse(input ?? {});
12254
+ return handler(client2, parsed);
12255
+ })
12256
+ );
12257
+ entries.push({ tool, registered });
12258
+ }
12259
+ const eager = options.eager === true || options.tiers !== void 0;
12260
+ const catalog = new DynamicToolCatalog(server, entries, scope, { eager });
12261
+ catalog.applyInitialEnablement();
12262
+ registerMetaTools(server, catalog, { includeToolkitLoader: options.tiers === void 0 });
12263
+ return { server, catalog };
12264
+ }
12265
+ var loadToolkitInputSchema = z4.object({
12266
+ name: z4.enum(CANONRY_MCP_TOOLKIT_NAMES).describe("Toolkit name. List options with canonry_help.")
12267
+ });
12268
+ function registerMetaTools(server, catalog, opts) {
12269
+ server.registerTool(
12270
+ "canonry_help",
12271
+ {
12272
+ title: "List Canonry MCP toolkits",
12273
+ description: "List available toolkits and which are loaded. Call before canonry_load_toolkit if unsure which to load.",
12274
+ inputSchema: {},
12275
+ annotations: { readOnlyHint: true }
12276
+ },
12277
+ async () => withToolErrors(async () => catalog.helpResult())
12278
+ );
12279
+ if (!opts.includeToolkitLoader) return;
12280
+ server.registerTool(
12281
+ "canonry_load_toolkit",
12282
+ {
12283
+ title: "Load a Canonry MCP toolkit",
12284
+ description: `Register a toolkit's tools for this session and emit one notifications/tools/list_changed. Idempotent. Loaded toolkits remain loaded for the rest of the session. Wait for this call to return before calling any newly enabled tool \u2014 pipelining the call with a tools/call on the same connection can race the registration and fail with "MCP error -32602: Tool ... disabled".`,
12285
+ inputSchema: loadToolkitInputSchema.shape,
12286
+ annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false }
12287
+ },
12288
+ async (input) => withToolErrors(async () => {
12289
+ const parsed = loadToolkitInputSchema.parse(input ?? {});
12290
+ return catalog.loadToolkit(parsed.name);
12291
+ })
12292
+ );
12293
+ }
12294
+ function getCanonryMcpTools(scope = "all", tiers) {
12295
+ const byScope = scope === "read-only" ? canonryMcpTools.filter((tool) => tool.access === "read") : [...canonryMcpTools];
12296
+ if (!tiers) return byScope;
12297
+ const wanted = new Set(tiers);
12298
+ return byScope.filter((tool) => wanted.has(tool.tier));
12299
+ }
12300
+
12301
+ // src/commands/skills.ts
12302
+ import crypto2 from "crypto";
12303
+ import fs2 from "fs";
12304
+ import os2 from "os";
12305
+ import path2 from "path";
12306
+ import { fileURLToPath } from "url";
12307
+ var BUNDLED_SKILL_NAMES = ["canonry", "aero"];
12308
+ function resolveBundledSkillsRoot(pkgDir) {
12309
+ const here = pkgDir ?? path2.dirname(fileURLToPath(import.meta.url));
12310
+ const candidates = [
12311
+ path2.join(here, "../assets/agent-workspace/skills"),
12312
+ path2.join(here, "../../assets/agent-workspace/skills"),
12313
+ path2.join(here, "../../../../skills")
12314
+ ];
12315
+ for (const candidate of candidates) {
12316
+ if (BUNDLED_SKILL_NAMES.every((name) => fs2.existsSync(path2.join(candidate, name, "SKILL.md")))) {
12317
+ return candidate;
12318
+ }
12319
+ }
12320
+ throw new CliError({
12321
+ code: "INTERNAL_ERROR",
12322
+ message: `Bundled skills not found. Searched:
12323
+ ${candidates.join("\n ")}`,
12324
+ exitCode: 2
12325
+ });
12326
+ }
12327
+ function parseDescription(content) {
12328
+ const fmMatch = /^---\n([\s\S]*?)\n---/.exec(content);
12329
+ if (!fmMatch) return "";
12330
+ const descMatch = /^description:\s*(\S.*)$/m.exec(fmMatch[1]);
12331
+ if (!descMatch) return "";
12332
+ return descMatch[1].replace(/^["']|["']$/g, "").trim();
12333
+ }
12334
+ function getBundledSkills(pkgDir) {
12335
+ const root = resolveBundledSkillsRoot(pkgDir);
12336
+ return BUNDLED_SKILL_NAMES.map((name) => {
12337
+ const skillDir = path2.join(root, name);
12338
+ const skillFile = path2.join(skillDir, "SKILL.md");
12339
+ const content = fs2.readFileSync(skillFile, "utf-8");
12340
+ return { name, description: parseDescription(content), bundledPath: skillDir };
12341
+ });
12342
+ }
12343
+ function walkRelative(dir, prefix = "") {
12344
+ const out = [];
12345
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
12346
+ const rel = prefix ? path2.join(prefix, entry.name) : entry.name;
12347
+ const full = path2.join(dir, entry.name);
12348
+ if (entry.isDirectory()) {
12349
+ out.push(...walkRelative(full, rel));
12350
+ } else if (entry.isFile()) {
12351
+ out.push(rel);
12352
+ }
12353
+ }
12354
+ return out.sort();
12355
+ }
12356
+ function sha256File(filePath) {
12357
+ return crypto2.createHash("sha256").update(fs2.readFileSync(filePath)).digest("hex");
12358
+ }
12359
+ function readSkillManifest(skillDir) {
12360
+ try {
12361
+ return coerceSkillManifest(JSON.parse(fs2.readFileSync(path2.join(skillDir, SKILL_MANIFEST_FILENAME), "utf-8")));
12362
+ } catch {
12363
+ return null;
12364
+ }
12365
+ }
12366
+ function writeSkillManifest(skillDir, manifest) {
12367
+ fs2.writeFileSync(path2.join(skillDir, SKILL_MANIFEST_FILENAME), `${JSON.stringify(manifest, null, 2)}
12368
+ `, "utf-8");
12369
+ }
12370
+ function reconcileSkillTree(srcDir, destDir, manifest, force) {
12371
+ const result = { added: [], updated: [], unchanged: [], conflicts: [], bundledHashes: /* @__PURE__ */ new Map() };
12372
+ for (const rel of walkRelative(srcDir)) {
12373
+ const srcPath = path2.join(srcDir, rel);
12374
+ const destPath = path2.join(destDir, rel);
12375
+ const bundledHash = sha256File(srcPath);
12376
+ result.bundledHashes.set(rel, bundledHash);
12377
+ const installedHash = fs2.existsSync(destPath) ? sha256File(destPath) : void 0;
12378
+ const state = classifySkillFile({ bundledHash, installedHash, manifestHash: manifest?.files[rel] });
12379
+ switch (state) {
12380
+ case "missing":
12381
+ fs2.mkdirSync(path2.dirname(destPath), { recursive: true });
12382
+ fs2.copyFileSync(srcPath, destPath);
12383
+ result.added.push(rel);
12384
+ break;
12385
+ case "unchanged":
12386
+ result.unchanged.push(rel);
12387
+ break;
12388
+ case "stale":
12389
+ fs2.copyFileSync(srcPath, destPath);
12390
+ result.updated.push(rel);
12391
+ break;
12392
+ case "edited":
12393
+ if (force) {
12394
+ fs2.copyFileSync(srcPath, destPath);
12395
+ result.updated.push(rel);
12396
+ } else {
12397
+ result.conflicts.push(rel);
12398
+ }
12399
+ break;
12400
+ }
12401
+ }
12402
+ return result;
12403
+ }
12404
+ function buildManifest(skillName, recon, prior) {
12405
+ const conflicts = new Set(recon.conflicts);
12406
+ const files = {};
12407
+ for (const [rel, bundledHash] of recon.bundledHashes) {
12408
+ files[rel] = conflicts.has(rel) ? prior?.files[rel] ?? bundledHash : bundledHash;
12409
+ }
12410
+ return { skill: skillName, version: PACKAGE_VERSION, files };
12411
+ }
12412
+ function describeChanges(recon) {
12413
+ const parts = [];
12414
+ if (recon.added.length > 0) parts.push(`${recon.added.length} added`);
12415
+ if (recon.updated.length > 0) parts.push(`${recon.updated.length} refreshed`);
12416
+ return parts.length > 0 ? parts.join(", ") : "no changes";
12417
+ }
12418
+ function buildClaudeMessage(name, status, recon) {
12419
+ const rel = `.claude/skills/${name}`;
12420
+ let message = status === "installed" ? `Installed ${rel}` : status === "updated" ? `Updated ${rel} (${describeChanges(recon)})` : `Already installed: ${rel}`;
12421
+ if (recon.conflicts.length > 0) {
12422
+ message += ` \u2014 ${recon.conflicts.length} file(s) differ from the bundle (local edits kept; pass --force to overwrite)`;
12423
+ }
12424
+ return message;
12425
+ }
12426
+ function installClaudeSkill(skill, targetDir, force) {
12427
+ const targetPath = path2.join(targetDir, ".claude", "skills", skill.name);
12428
+ const existedBefore = fs2.existsSync(path2.join(targetPath, "SKILL.md"));
12429
+ const priorManifest = readSkillManifest(targetPath);
12430
+ fs2.mkdirSync(targetPath, { recursive: true });
12431
+ const recon = reconcileSkillTree(skill.bundledPath, targetPath, priorManifest, force);
12432
+ writeSkillManifest(targetPath, buildManifest(skill.name, recon, priorManifest));
12433
+ const changed = recon.added.length + recon.updated.length;
12434
+ const status = !existedBefore ? "installed" : changed > 0 ? "updated" : "already-installed";
12435
+ return {
12436
+ skill: skill.name,
12437
+ client: CodingAgents.claude,
12438
+ targetPath,
12439
+ status,
12440
+ message: buildClaudeMessage(skill.name, status, recon),
12441
+ added: recon.added,
12442
+ updated: recon.updated,
12443
+ unchanged: recon.unchanged,
12444
+ conflicts: recon.conflicts
12445
+ };
12446
+ }
12447
+ function installCodexSymlink(skill, targetDir, force) {
12448
+ const codexPath = path2.join(targetDir, ".codex", "skills", skill.name);
12449
+ const claudePath = path2.join(targetDir, ".claude", "skills", skill.name);
12450
+ const linkTarget = path2.relative(path2.dirname(codexPath), claudePath);
12451
+ fs2.mkdirSync(path2.dirname(codexPath), { recursive: true });
12452
+ let stat;
12453
+ try {
12454
+ stat = fs2.lstatSync(codexPath);
12455
+ } catch {
12456
+ stat = void 0;
12457
+ }
12458
+ if (stat?.isSymbolicLink()) {
12459
+ const existing = fs2.readlinkSync(codexPath);
12460
+ if (existing === linkTarget) {
12461
+ return {
12462
+ skill: skill.name,
12463
+ client: CodingAgents.codex,
12464
+ targetPath: codexPath,
12465
+ status: "already-linked",
12466
+ message: `Already linked: .codex/skills/${skill.name}`
12467
+ };
12468
+ }
12469
+ if (!force) {
12470
+ throw new CliError({
12471
+ code: "VALIDATION_ERROR",
12472
+ message: `.codex/skills/${skill.name} is a symlink pointing elsewhere (${existing}). Pass --force to relink.`,
12473
+ details: { skill: skill.name, targetPath: codexPath, existingTarget: existing },
12474
+ exitCode: 1
12475
+ });
12476
+ }
12477
+ fs2.unlinkSync(codexPath);
12478
+ fs2.symlinkSync(linkTarget, codexPath);
12479
+ return {
12480
+ skill: skill.name,
12481
+ client: CodingAgents.codex,
12482
+ targetPath: codexPath,
12483
+ status: "relinked",
12484
+ message: `Relinked .codex/skills/${skill.name} \u2192 ${linkTarget}`
12485
+ };
12486
+ }
12487
+ if (stat) {
12488
+ if (!force) {
12489
+ throw new CliError({
12490
+ code: "VALIDATION_ERROR",
12491
+ message: `.codex/skills/${skill.name} exists but is not a symlink. Pass --force to replace.`,
12492
+ details: { skill: skill.name, targetPath: codexPath },
12493
+ exitCode: 1
12494
+ });
12495
+ }
12496
+ fs2.rmSync(codexPath, { recursive: true, force: true });
12497
+ }
12498
+ fs2.symlinkSync(linkTarget, codexPath);
12499
+ return {
12500
+ skill: skill.name,
12501
+ client: CodingAgents.codex,
12502
+ targetPath: codexPath,
12503
+ status: stat ? "relinked" : "linked",
12504
+ message: stat ? `Replaced and linked .codex/skills/${skill.name} \u2192 ${linkTarget}` : `Linked .codex/skills/${skill.name} \u2192 ${linkTarget}`
12505
+ };
12506
+ }
12507
+ function buildSummaryMessage(results) {
12508
+ const counts = {};
12509
+ for (const r of results) counts[r.status] = (counts[r.status] ?? 0) + 1;
12510
+ const parts = Object.entries(counts).map(([status, n]) => `${n} ${status}`);
12511
+ let message = `Skills install summary: ${parts.join(", ")}.`;
12512
+ const totalConflicts = results.reduce((sum, r) => sum + (r.conflicts?.length ?? 0), 0);
12513
+ if (totalConflicts > 0) {
12514
+ message += ` ${totalConflicts} file(s) differ from the bundle and were kept \u2014 pass --force to overwrite local edits.`;
12515
+ }
12516
+ return message;
12517
+ }
12518
+ function getBundledSkillSnapshots(pkgDir) {
12519
+ return getBundledSkills(pkgDir).map((skill) => {
12520
+ const files = {};
12521
+ for (const rel of walkRelative(skill.bundledPath)) {
12522
+ files[rel] = sha256File(path2.join(skill.bundledPath, rel));
12523
+ }
12524
+ return {
12525
+ name: skill.name,
12526
+ version: PACKAGE_VERSION,
12527
+ files,
12528
+ description: readBundledSkillDescription(skill.bundledPath)
12529
+ };
12530
+ });
12531
+ }
12532
+ function readBundledSkillDescription(skillDir) {
12533
+ let raw;
12534
+ try {
12535
+ raw = fs2.readFileSync(path2.join(skillDir, "SKILL.md"), "utf-8");
12536
+ } catch {
12537
+ return void 0;
12538
+ }
12539
+ const parts = raw.split("---");
12540
+ if (parts.length < 3) return void 0;
12541
+ const lines = (parts[1] ?? "").split("\n");
12542
+ const collected = [];
12543
+ let inside = false;
12544
+ for (const line of lines) {
12545
+ const isTopLevelKey = /^[a-z][\w-]*:/i.test(line);
12546
+ if (inside && isTopLevelKey) break;
12547
+ if (isTopLevelKey && line.startsWith("description:")) {
12548
+ inside = true;
12549
+ collected.push(line.slice("description:".length).trim());
12550
+ continue;
12551
+ }
12552
+ if (inside) collected.push(line.trim());
12553
+ }
12554
+ if (!inside) return void 0;
12555
+ const value = collected.join(" ").trim();
12556
+ const unquoted = value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
12557
+ return unquoted.length > 0 ? unquoted : void 0;
12558
+ }
12559
+ async function installSkills(opts = {}) {
12560
+ const targetDir = opts.user ? os2.homedir() : path2.resolve(opts.dir ?? process.cwd());
12561
+ const client2 = opts.client ?? SkillsClients.all;
12562
+ const force = opts.force ?? false;
12563
+ const allSkills = getBundledSkills();
12564
+ const requestedNames = opts.skills && opts.skills.length > 0 ? opts.skills : allSkills.map((s) => s.name);
12565
+ const knownNames = new Set(allSkills.map((s) => s.name));
12566
+ const unknown = requestedNames.filter((n) => !knownNames.has(n));
12567
+ if (unknown.length > 0) {
12568
+ throw new CliError({
12569
+ code: "VALIDATION_ERROR",
12570
+ message: `Unknown skill(s): ${unknown.join(", ")}. Available: ${[...knownNames].join(", ")}`,
12571
+ details: { unknownSkills: unknown, availableSkills: [...knownNames] },
12572
+ exitCode: 1
12573
+ });
12574
+ }
12575
+ const skillsToInstall = allSkills.filter((s) => requestedNames.includes(s.name));
12576
+ fs2.mkdirSync(targetDir, { recursive: true });
12577
+ const results = [];
12578
+ for (const skill of skillsToInstall) {
12579
+ results.push(installClaudeSkill(skill, targetDir, force));
12580
+ if (client2 !== SkillsClients.claude) {
12581
+ results.push(installCodexSymlink(skill, targetDir, force));
12582
+ }
12583
+ }
12584
+ return {
12585
+ targetDir,
12586
+ results,
12587
+ message: buildSummaryMessage(results)
12588
+ };
12589
+ }
12590
+ async function listSkills(opts = {}) {
12591
+ const skills = getBundledSkills();
12592
+ if (isMachineFormat(opts.format)) {
12593
+ console.log(JSON.stringify({
12594
+ skills: skills.map((s) => ({
12595
+ name: s.name,
12596
+ description: s.description,
12597
+ claudePath: `.claude/skills/${s.name}`,
12598
+ codexPath: `.codex/skills/${s.name}`
12599
+ }))
12600
+ }, null, 2));
12601
+ return;
12602
+ }
12603
+ console.log("Bundled canonry skills:\n");
12604
+ for (const skill of skills) {
12605
+ console.log(` ${skill.name}`);
12606
+ if (skill.description) console.log(` ${skill.description}`);
12607
+ console.log(` Claude: .claude/skills/${skill.name}/`);
12608
+ console.log(` Codex: .codex/skills/${skill.name} (symlink \u2192 ../../.claude/skills/${skill.name})`);
12609
+ console.log();
12610
+ }
12611
+ }
12612
+ function emitInstallSummary(summary, format) {
12613
+ if (isMachineFormat(format)) {
12614
+ console.log(JSON.stringify(summary, null, 2));
12615
+ return;
12616
+ }
12617
+ for (const r of summary.results) console.log(r.message);
12618
+ console.log(`
12619
+ Target: ${summary.targetDir}`);
12620
+ console.log(summary.message);
12621
+ }
12622
+ function getMissingUserSkillsNudge(home, agentPlugin) {
12623
+ if (!home) return null;
12624
+ const skillsBase = path2.join(home, ".claude", "skills");
12625
+ const installed = [];
12626
+ const missing = [];
12627
+ for (const name of BUNDLED_SKILL_NAMES) {
12628
+ const skillFile = path2.join(skillsBase, name, "SKILL.md");
12629
+ if (existsSafe(skillFile)) installed.push(name);
12630
+ else missing.push(name);
12631
+ }
12632
+ if (agentPlugin && agentPlugin.configuredClients.length > 0) {
12633
+ const unverifiedClients = agentPlugin.configuredClients.filter((client2) => !agentPlugin.verifiedClients.includes(client2));
12634
+ const mismatchedClients = agentPlugin.verifiedClients.filter((client2) => agentPlugin.verifiedClientVersions?.[client2] !== PACKAGE_VERSION);
12635
+ if (unverifiedClients.length === 0 && mismatchedClients.length === 0) return null;
12636
+ const displayClients = (clients) => clients.map((client2) => client2 === "claude-code" ? "Claude Code" : "Codex").join(" + ");
12637
+ const problems = [];
12638
+ if (unverifiedClients.length > 0) {
12639
+ problems.push(`The Canonry plugin is enabled for ${displayClients(unverifiedClients)}, but its cached manifest and skill assets could not be verified.`);
12640
+ }
12641
+ if (mismatchedClients.length > 0) {
12642
+ const versions = mismatchedClients.map((client2) => `${client2 === "claude-code" ? "Claude Code" : "Codex"} v${agentPlugin.verifiedClientVersions?.[client2] ?? "unknown"}`).join(", ");
12643
+ problems.push(`${versions} ${mismatchedClients.length === 1 ? "does" : "do"} not match the running Canonry v${PACKAGE_VERSION}.`);
12644
+ }
12645
+ return {
12646
+ message: `Tip: ${problems.join(" ")} Update or reinstall \`canonry@canonry\` with the affected client plugin manager.`,
12647
+ missing,
12648
+ installed
12649
+ };
12650
+ }
12651
+ if (missing.length === 0) return null;
12652
+ const fix = missing.length === BUNDLED_SKILL_NAMES.length ? "canonry skills install --user" : `canonry skills install ${missing.join(" ")} --user`;
12653
+ return {
12654
+ message: `Tip: ${missing.join(" + ")} skill${missing.length === 1 ? "" : "s"} not installed in ~/.claude/skills/. Run \`${fix}\` so Claude/Codex sessions on this host auto-load the canonry reference docs.`,
12655
+ missing,
12656
+ installed
12657
+ };
12658
+ }
12659
+ function existsSafe(p) {
12660
+ try {
12661
+ return fs2.existsSync(p);
12662
+ } catch {
12663
+ return false;
12664
+ }
12665
+ }
12666
+ function parseSkillsClient(value) {
12667
+ if (!value) return SkillsClients.all;
12668
+ const parsed = skillsClientSchema.safeParse(value);
12669
+ if (parsed.success) return parsed.data;
12670
+ const allowed = skillsClientSchema.options;
12671
+ throw new CliError({
12672
+ code: "VALIDATION_ERROR",
12673
+ message: `Invalid --client value "${value}". Must be one of: ${allowed.join(", ")}`,
12674
+ details: { flag: "client", value, allowed },
12675
+ exitCode: 1
12676
+ });
12677
+ }
12678
+
12232
12679
  export {
12233
12680
  getConfigDir,
12234
12681
  getConfigPath,
@@ -12258,5 +12705,7 @@ export {
12258
12705
  measurementDraftOperationSchema,
12259
12706
  runMeasurementDraftAction,
12260
12707
  canonryMcpTools,
12261
- CanonryMcpToolNames
12708
+ CanonryMcpToolNames,
12709
+ CANONRY_MCP_TOOLKIT_NAMES,
12710
+ createCanonryMcpServer
12262
12711
  };