@ainyc/canonry 4.180.7 → 4.182.3

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 (41) hide show
  1. package/README.md +80 -45
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +13 -10
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +7 -6
  4. package/assets/assets/{AuditHistoryPanel-B1lmI7wm.js → AuditHistoryPanel-1_FHpzj-.js} +1 -1
  5. package/assets/assets/{BacklinksPage-Bw61uQu8.js → BacklinksPage-045za10_.js} +1 -1
  6. package/assets/assets/{ChartPrimitives-DG35TyhH.js → ChartPrimitives-DLEICrrL.js} +1 -1
  7. package/assets/assets/{HistoryPage-D8z9-NkT.js → HistoryPage-36K_6asu.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-CXX9GFj8.js → MeasurementPropertyPage-B4DJGoY6.js} +1 -1
  9. package/assets/assets/ProjectPage-CshC68Ne.js +9 -0
  10. package/assets/assets/{RunRow-DXHy16T3.js → RunRow-U2CN755e.js} +1 -1
  11. package/assets/assets/{RunsPage-D2aE620i.js → RunsPage-D0CtU4d-.js} +1 -1
  12. package/assets/assets/SettingsPage-zMWieTPK.js +1 -0
  13. package/assets/assets/SiteHealthSection-TpQPu_Uj.js +4 -0
  14. package/assets/assets/{TrafficPage-DFHWgvkU.js → TrafficPage-DfLUt-64.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-DxjyRL8Y.js → TrafficSourceDetailPage-DbjNNWSt.js} +1 -1
  16. package/assets/assets/extract-error-message-CWqz1QPC.js +1 -0
  17. package/assets/assets/index-Brb7M_6G.js +86 -0
  18. package/assets/assets/index-DwdK2e4e.css +1 -0
  19. package/assets/assets/{react-sigma_core.esm.min-DAcOZae7.js → react-sigma_core.esm.min-Drqprr9L.js} +1 -1
  20. package/assets/assets/{vendor-lucide-nYKv-BB7.js → vendor-lucide-XjCNFK0x.js} +1 -1
  21. package/assets/assets/{vendor-markdown-Bn7fRwrx.js → vendor-markdown-9tTeP8v7.js} +1 -1
  22. package/assets/assets/{vendor-radix-4UOaVWza.js → vendor-radix-BpmvLvpp.js} +1 -1
  23. package/assets/assets/{vendor-recharts-D8rt5lKn.js → vendor-recharts-FA0uySU7.js} +1 -1
  24. package/assets/assets/{vendor-tanstack-Dock0eH1.js → vendor-tanstack-CYBjbqVr.js} +1 -1
  25. package/assets/index.html +7 -7
  26. package/dist/{chunk-VZO6CLHY.js → chunk-BDJT7XZL.js} +169 -64
  27. package/dist/{chunk-RXXZ5BU5.js → chunk-DY5ZDKQW.js} +183 -1
  28. package/dist/{chunk-CWXPBQR5.js → chunk-FVC7PGYQ.js} +47 -5
  29. package/dist/{chunk-XYTSKRO3.js → chunk-I3KSOMJA.js} +2 -2
  30. package/dist/{chunk-WJFBNCVD.js → chunk-K45YKXE4.js} +4310 -3323
  31. package/dist/cli.js +323 -99
  32. package/dist/index.js +4 -4
  33. package/dist/{intelligence-service-MEPYXRBS.js → intelligence-service-HGA553GT.js} +2 -2
  34. package/dist/mcp.js +3 -3
  35. package/package.json +7 -7
  36. package/assets/assets/ProjectPage-BaN_rvoV.js +0 -9
  37. package/assets/assets/SettingsPage-BiiFqarS.js +0 -1
  38. package/assets/assets/SiteHealthSection-DCH2Ejtc.js +0 -4
  39. package/assets/assets/extract-error-message-BdXUZQW9.js +0 -1
  40. package/assets/assets/index-BuEq5Rhg.css +0 -1
  41. package/assets/assets/index-CSictO9U.js +0 -83
@@ -78,6 +78,14 @@ function runInProgress(projectName) {
78
78
  function operationInProgress(message, details) {
79
79
  return new AppError("OPERATION_IN_PROGRESS", message, 409, details);
80
80
  }
81
+ function scheduleVersionConflict(expectedUpdatedAt, actualUpdatedAt) {
82
+ return new AppError(
83
+ "SCHEDULE_VERSION_CONFLICT",
84
+ "The schedule changed since it was loaded. Reload it before saving.",
85
+ 409,
86
+ { expectedUpdatedAt, actualUpdatedAt }
87
+ );
88
+ }
81
89
  function measurementPlanRevisionConflict(expectedActiveRevision, actualActiveRevision) {
82
90
  return new AppError(
83
91
  "MEASUREMENT_PLAN_REVISION_CONFLICT",
@@ -6715,6 +6723,22 @@ var measurementDraftUpsertCompetitorRequestSchema = z15.object({
6715
6723
  groupKey: measurementV2StableKeySchema,
6716
6724
  competitor: measurementDraftCompetitorSchema
6717
6725
  }).strict();
6726
+ var measurementDraftPinCompetitorRequestSchema = z15.object({
6727
+ expectedActiveRevision: measurementDraftRevisionSchema,
6728
+ groupKey: measurementV2StableKeySchema,
6729
+ domain: z15.string().trim().min(1).refine((value) => hostOf(value) !== null, "A competitor domain must be a valid hostname"),
6730
+ label: z15.string().trim().min(1).optional(),
6731
+ aliases: z15.array(z15.string().trim().min(1)).optional()
6732
+ }).strict();
6733
+ var measurementDraftPinCompetitorResponseSchema = measurementDraftMutationResponseSchema.extend({
6734
+ groupKey: measurementV2StableKeySchema,
6735
+ competitor: measurementDraftCompetitorSchema,
6736
+ draftCreated: z15.boolean(),
6737
+ published: z15.object({
6738
+ revision: measurementDraftRevisionSchema,
6739
+ competitorsChanged: z15.literal(false)
6740
+ }).strict()
6741
+ }).strict();
6718
6742
  var measurementDraftRemoveCompetitorRequestSchema = z15.object({
6719
6743
  groupKey: measurementV2StableKeySchema,
6720
6744
  competitorKey: measurementV2StableKeySchema
@@ -6818,6 +6842,8 @@ var measurementSetupResponseSchema = z15.object({
6818
6842
  state: measurementSetupStateSchema,
6819
6843
  nextAction: measurementSetupNextActionSchema,
6820
6844
  mode: measurementSetupModeSchema,
6845
+ /** Whether this project's selected provider roster contains an engine the host can execute now. */
6846
+ answerVisibilityProviderReady: z15.boolean(),
6821
6847
  activeRevision: measurementDraftRevisionSchema.nullable(),
6822
6848
  activeSchemaVersion: z15.union([z15.literal(1), z15.literal(2)]).nullable(),
6823
6849
  draft: z15.object({
@@ -10748,6 +10774,11 @@ var scheduleDtoSchema = z31.object({
10748
10774
  createdAt: z31.string(),
10749
10775
  updatedAt: z31.string()
10750
10776
  });
10777
+ var scheduleExpectedUpdatedAtSchema = z31.string().datetime();
10778
+ function nextScheduleUpdatedAt(previous, nowMs = Date.now()) {
10779
+ const previousMs = previous === void 0 ? Number.NaN : Date.parse(previous);
10780
+ return new Date(Number.isFinite(previousMs) ? Math.max(nowMs, previousMs + 1) : nowMs).toISOString();
10781
+ }
10751
10782
  var scheduleUpsertRequestSchema = z31.object({
10752
10783
  /** Run kind. Defaults to 'answer-visibility' so existing callers don't have to change. */
10753
10784
  kind: schedulableRunKindSchema.optional(),
@@ -10757,7 +10788,12 @@ var scheduleUpsertRequestSchema = z31.object({
10757
10788
  enabled: z31.boolean().optional().default(true),
10758
10789
  providers: z31.array(providerNameSchema).optional().default([]),
10759
10790
  /** Required when kind === 'traffic-sync'. Forbidden for other kinds. Validated server-side. */
10760
- sourceId: z31.string().optional()
10791
+ sourceId: z31.string().optional(),
10792
+ /**
10793
+ * Optimistic concurrency guard. A timestamp updates only that exact version;
10794
+ * null creates only while the schedule is absent. Omit for legacy behavior.
10795
+ */
10796
+ expectedUpdatedAt: scheduleExpectedUpdatedAtSchema.nullable().optional()
10761
10797
  }).refine(
10762
10798
  (data) => data.preset && !data.cron || !data.preset && data.cron,
10763
10799
  { message: 'Exactly one of "preset" or "cron" must be provided' }
@@ -11815,6 +11851,143 @@ var sourceBreakdownDtoSchema = z35.object({
11815
11851
  /** Applied ranked-list limit; null when the full list is returned. */
11816
11852
  limit: z35.number().int().nullable()
11817
11853
  });
11854
+ var competitorLandscapeSurfaceClassSchema = z35.enum([
11855
+ "own",
11856
+ "direct-competitor",
11857
+ "ota-aggregator",
11858
+ "editorial-media",
11859
+ "other",
11860
+ "unknown"
11861
+ ]);
11862
+ var competitorLandscapeScopeSchema = z35.discriminatedUnion("kind", [
11863
+ z35.object({ kind: z35.literal("project") }).strict(),
11864
+ z35.object({ kind: z35.literal("group"), groupKey: z35.string().trim().min(1) }).strict(),
11865
+ z35.object({ kind: z35.literal("all-markets") }).strict()
11866
+ ]);
11867
+ var competitorLandscapeQueryClassSchema = z35.enum(["all", "branded", "non-brand"]);
11868
+ var competitorLandscapeModeSchema = z35.enum(["project", "all-markets"]);
11869
+ var competitorLandscapeQuerySchema = z35.object({
11870
+ window: metricsWindowSchema.optional(),
11871
+ groupKey: z35.string().trim().min(1).optional(),
11872
+ scope: competitorLandscapeModeSchema.optional(),
11873
+ provider: z35.string().trim().min(1).optional(),
11874
+ /** Exact requested model ID; provider is required to disambiguate model names. */
11875
+ model: modelIdSchema.optional(),
11876
+ /** Opt-in agent comparison; the default landscape remains pooled. */
11877
+ groupBy: z35.literal("model").optional(),
11878
+ queryClass: competitorLandscapeQueryClassSchema.optional(),
11879
+ location: z35.string().trim().min(1).optional(),
11880
+ runId: z35.string().trim().min(1).optional()
11881
+ }).strict().superRefine((value, context) => {
11882
+ if (value.scope === "all-markets" && value.groupKey !== void 0) {
11883
+ context.addIssue({
11884
+ code: "custom",
11885
+ path: ["groupKey"],
11886
+ message: "groupKey cannot be combined with scope=all-markets"
11887
+ });
11888
+ }
11889
+ if (value.model !== void 0 && value.provider === void 0) {
11890
+ context.addIssue({
11891
+ code: "custom",
11892
+ path: ["provider"],
11893
+ message: "provider is required when filtering by model"
11894
+ });
11895
+ }
11896
+ });
11897
+ var competitorLandscapeRowSchema = z35.object({
11898
+ domain: z35.string().trim().min(1),
11899
+ label: z35.string().trim().min(1),
11900
+ surfaceClass: competitorLandscapeSurfaceClassSchema,
11901
+ pinned: z35.boolean(),
11902
+ /** One answer-text match at most per result. */
11903
+ mentionCount: z35.number().int().nonnegative(),
11904
+ /**
11905
+ * Percentage points (0..100). Null outside the competitive denominator, and
11906
+ * null for any selection that pools query classes: a brand wins its own
11907
+ * branded queries by definition, so a pooled ratio flatters the project and
11908
+ * buries every competitor. Counts stay populated either way.
11909
+ */
11910
+ shareOfVoice: z35.number().min(0).max(100).nullable(),
11911
+ /** One source-list credit at most per result. Independent of mentions. */
11912
+ citationCount: z35.number().int().nonnegative(),
11913
+ /** Answer-text result count behind the mention field. */
11914
+ answeredResults: z35.number().int().nonnegative(),
11915
+ firstSeenAt: z35.string().datetime().nullable(),
11916
+ lastSeenAt: z35.string().datetime().nullable(),
11917
+ sampleUrls: z35.array(z35.string().url()).max(3)
11918
+ }).strict();
11919
+ var competitorLandscapeEvidenceSchema = z35.object({
11920
+ answeredResults: z35.number().int().nonnegative(),
11921
+ sourceResults: z35.number().int().nonnegative(),
11922
+ missingAnswerTextResults: z35.number().int().nonnegative(),
11923
+ /** Project plus direct-competitor named credits behind SOV. */
11924
+ mentionCredits: z35.number().int().nonnegative(),
11925
+ /** Citation captures that cannot prove a complete source list; never inferred as misses. */
11926
+ incompleteSourceResults: z35.number().int().nonnegative(),
11927
+ /** Stored probe snapshots intentionally omitted from every landscape metric. */
11928
+ excludedProbeResults: z35.number().int().nonnegative(),
11929
+ /** Stored snapshots from queued/running/failed/cancelled runs omitted from every metric. */
11930
+ excludedNonCompletedResults: z35.number().int().nonnegative()
11931
+ }).strict();
11932
+ var COMPETITOR_LANDSCAPE_MODEL_GROUP_LIMIT = 50;
11933
+ var competitorLandscapeModelGroupSchema = z35.object({
11934
+ provider: z35.string().trim().min(1),
11935
+ /** Null preserves historical observations with unknown requested model identity. */
11936
+ model: modelIdSchema.nullable(),
11937
+ /** Raw upstream-reported identity; never filled from the requested model or current settings. */
11938
+ servedModels: modelEvidenceStateSchema,
11939
+ snapshotCount: z35.number().int().positive(),
11940
+ project: competitorLandscapeRowSchema,
11941
+ pinned: z35.array(competitorLandscapeRowSchema),
11942
+ observed: z35.array(competitorLandscapeRowSchema),
11943
+ otherSources: z35.array(competitorLandscapeRowSchema),
11944
+ evidence: competitorLandscapeEvidenceSchema,
11945
+ /** Per-group ranked-row truncation; all pins and full-population denominators remain included. */
11946
+ truncated: z35.boolean()
11947
+ }).strict();
11948
+ var competitorLandscapeModelComparisonSchema = z35.object({
11949
+ basis: z35.literal("requested-model"),
11950
+ groups: z35.array(competitorLandscapeModelGroupSchema).max(COMPETITOR_LANDSCAPE_MODEL_GROUP_LIMIT),
11951
+ totalGroups: z35.number().int().nonnegative(),
11952
+ /** Groups sort by provider and requested model. Use provider/model filters to narrow a truncated result. */
11953
+ truncated: z35.boolean()
11954
+ }).strict();
11955
+ var competitorLandscapeResponseSchema = z35.object({
11956
+ window: metricsWindowSchema,
11957
+ scope: competitorLandscapeScopeSchema,
11958
+ project: competitorLandscapeRowSchema,
11959
+ /** Explicit/custom competitors, preserved even with zero observations. */
11960
+ pinned: z35.array(competitorLandscapeRowSchema),
11961
+ /** Stored-discovery direct competitors, ordered by historical mention share. */
11962
+ observed: z35.array(competitorLandscapeRowSchema),
11963
+ /** Aggregators, editorial, unknown, and other cited sources; never share-of-voice competitors. */
11964
+ otherSources: z35.array(competitorLandscapeRowSchema),
11965
+ evidence: competitorLandscapeEvidenceSchema,
11966
+ /** Present only when groupBy=model; pooled fields remain available and unchanged. */
11967
+ modelComparison: competitorLandscapeModelComparisonSchema.optional(),
11968
+ /** Present only for Advanced market reads; active metrics stay frozen while draft pins remain pending publish. */
11969
+ marketState: z35.object({
11970
+ activeRevision: z35.number().int().positive(),
11971
+ draft: z35.object({
11972
+ etag: z35.string().trim().min(1),
11973
+ /** Draft-only market identities included in `pinned` for this response. */
11974
+ pendingCompetitorDomains: z35.array(z35.string().trim().min(1))
11975
+ }).strict().nullable()
11976
+ }).strict().nullable(),
11977
+ /** Echoes all applied filters so a client never mistakes a scoped reading for a project-wide one. */
11978
+ filters: z35.object({
11979
+ scope: competitorLandscapeModeSchema,
11980
+ groupKey: z35.string().nullable(),
11981
+ provider: z35.string().nullable(),
11982
+ model: modelIdSchema.optional(),
11983
+ groupBy: z35.literal("model").optional(),
11984
+ queryClass: competitorLandscapeQueryClassSchema,
11985
+ location: z35.string().nullable(),
11986
+ runId: z35.string().nullable()
11987
+ }).strict(),
11988
+ /** True when ranked observed/source lists exceed the server cap; pinned rows are never dropped. */
11989
+ truncated: z35.boolean()
11990
+ }).strict();
11818
11991
  function parseWindow(value) {
11819
11992
  if (value === void 0 || value === "") return "all";
11820
11993
  const parsed = metricsWindowSchema.safeParse(value);
@@ -16436,6 +16609,7 @@ export {
16436
16609
  noQueries,
16437
16610
  runInProgress,
16438
16611
  operationInProgress,
16612
+ scheduleVersionConflict,
16439
16613
  measurementPlanRevisionConflict,
16440
16614
  measurementCompiledChecksumConflict,
16441
16615
  measurementRunRevisionMismatch,
@@ -16568,6 +16742,7 @@ export {
16568
16742
  measurementChangesResponseSchema,
16569
16743
  measurementDataQualityQuerySchema,
16570
16744
  measurementDataQualityResponseSchema,
16745
+ measurementDraftCompetitorSchema,
16571
16746
  measurementDraftAuthoringSchema,
16572
16747
  MEASUREMENT_DRAFT_MAX_ASSIGNMENTS_PER_ACTION,
16573
16748
  MEASUREMENT_DRAFT_MAX_GROUPS,
@@ -16600,6 +16775,8 @@ export {
16600
16775
  measurementDraftUpsertGroupRequestSchema,
16601
16776
  measurementDraftRemoveGroupRequestSchema,
16602
16777
  measurementDraftUpsertCompetitorRequestSchema,
16778
+ measurementDraftPinCompetitorRequestSchema,
16779
+ measurementDraftPinCompetitorResponseSchema,
16603
16780
  measurementDraftRemoveCompetitorRequestSchema,
16604
16781
  measurementDraftCompilePreviewResponseSchema,
16605
16782
  measurementDraftDiffPreviewResponseSchema,
@@ -16854,6 +17031,8 @@ export {
16854
17031
  schedulableRunKindSchema,
16855
17032
  SchedulableRunKinds,
16856
17033
  scheduleDtoSchema,
17034
+ scheduleExpectedUpdatedAtSchema,
17035
+ nextScheduleUpdatedAt,
16857
17036
  scheduleUpsertRequestSchema,
16858
17037
  categorizeSource,
16859
17038
  categorizeSourceWithCompetitors,
@@ -16897,6 +17076,9 @@ export {
16897
17076
  modelIdsEquivalent,
16898
17077
  brandMetricsDtoSchema,
16899
17078
  sourceBreakdownDtoSchema,
17079
+ competitorLandscapeQuerySchema,
17080
+ COMPETITOR_LANDSCAPE_MODEL_GROUP_LIMIT,
17081
+ competitorLandscapeResponseSchema,
16900
17082
  parseWindow,
16901
17083
  windowCutoff,
16902
17084
  resolveDateRange,
@@ -25,6 +25,7 @@ import {
25
25
  classifySkillFile,
26
26
  coerceSkillManifest,
27
27
  competitorBatchRequestSchema,
28
+ competitorLandscapeQuerySchema,
28
29
  describeError,
29
30
  discoveryBucketSchema,
30
31
  discoveryCompetitorTypeSchema,
@@ -92,7 +93,7 @@ import {
92
93
  trafficConnectWordpressRequestSchema,
93
94
  trafficEventKindSchema,
94
95
  trafficSeriesGranularitySchema
95
- } from "./chunk-RXXZ5BU5.js";
96
+ } from "./chunk-DY5ZDKQW.js";
96
97
 
97
98
  // src/config.ts
98
99
  import fs from "fs";
@@ -160,8 +161,8 @@ function loadConfig() {
160
161
  if (!fs.existsSync(configPath)) {
161
162
  throw new Error(
162
163
  `Config not found at ${configPath}.
163
- Run "canonry init" to set up interactively, or "canonry init --gemini-key <key>" for non-interactive setup.
164
- For CI/Docker, use "canonry bootstrap" with env vars (GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, PERPLEXITY_API_KEY, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET).`
164
+ Run "canonry bootstrap" to create the local Page Health runtime; provider credentials are optional.
165
+ Use "canonry init" instead only when you want interactive provider/OAuth provisioning.`
165
166
  );
166
167
  }
167
168
  const raw = fs.readFileSync(configPath, "utf-8");
@@ -174,8 +175,10 @@ For CI/Docker, use "canonry bootstrap" with env vars (GEMINI_API_KEY, OPENAI_API
174
175
  ].filter(Boolean).join(", ");
175
176
  throw new Error(
176
177
  `Invalid config at ${configPath} \u2014 missing: ${missing}.
177
- These fields are auto-generated. Run "canonry init" (or "canonry init --gemini-key <key>" for non-interactive setup) to create a valid config.
178
- Do not write config.yaml by hand; use "canonry init", "canonry settings", or "canonry bootstrap" instead.`
178
+ Back up this config privately before you repair it.
179
+ Restore the missing values from a known-good backup. If none exists, recover the original values before you retry.
180
+ Keep the original API key and database path. Do not share secrets.
181
+ Do not use "canonry init --force" for recovery. It replaces credentials without a backup.`
179
182
  );
180
183
  }
181
184
  if (parsed.geminiApiKey && !parsed.providers?.gemini) {
@@ -2396,6 +2399,18 @@ var postApiV1ProjectsByNameCompetitors = (options) => {
2396
2399
  }
2397
2400
  });
2398
2401
  };
2402
+ var getApiV1ProjectsByNameAnalyticsCompetitors = (options) => {
2403
+ return (options.client ?? client).get({
2404
+ security: [
2405
+ {
2406
+ scheme: "bearer",
2407
+ type: "http"
2408
+ }
2409
+ ],
2410
+ url: "/api/v1/projects/{name}/analytics/competitors",
2411
+ ...options
2412
+ });
2413
+ };
2399
2414
  var getApiV1ProjectsByNameRuns = (options) => {
2400
2415
  return (options.client ?? client).get({
2401
2416
  security: [
@@ -6581,6 +6596,16 @@ var ApiClient = class {
6581
6596
  })
6582
6597
  );
6583
6598
  }
6599
+ /** Stored competitor SOV, optionally grouped or filtered by requested model; never starts provider work. */
6600
+ async getCompetitorLandscape(project, opts = {}) {
6601
+ return this.invoke(
6602
+ () => getApiV1ProjectsByNameAnalyticsCompetitors({
6603
+ client: this.heyClient,
6604
+ path: { name: project },
6605
+ query: opts
6606
+ })
6607
+ );
6608
+ }
6584
6609
  async getAnalyticsGaps(project, window) {
6585
6610
  return this.invoke(
6586
6611
  () => getApiV1ProjectsByNameAnalyticsGaps({
@@ -9086,6 +9111,9 @@ var competitorsInputSchema = z3.object({
9086
9111
  project: projectNameSchema,
9087
9112
  request: competitorBatchRequestSchema
9088
9113
  });
9114
+ var competitorLandscapeInputSchema = competitorLandscapeQuerySchema.safeExtend({
9115
+ project: projectNameSchema
9116
+ }).strict();
9089
9117
  var projectUpsertInputSchema = z3.object({
9090
9118
  project: projectNameSchema,
9091
9119
  request: projectUpsertRequestSchema
@@ -9478,6 +9506,20 @@ var canonryMcpTools = [
9478
9506
  openApiOperations: ["GET /api/v1/projects/{name}/analytics/sources"],
9479
9507
  handler: (client2, input) => client2.getAnalyticsSources(input.project, { window: input.window, limit: input.limit })
9480
9508
  }),
9509
+ defineTool({
9510
+ name: "canonry_competitor_landscape",
9511
+ title: "Get historical competitor landscape",
9512
+ description: "Returns pinned competitors first, then observed direct competitors and other cited sources from stored answer/source evidence only. Mention share is percentage points (0..100) from answer text; citations are independent. Share of voice needs ONE query class: `queryClass=all`, and omitting it, pool branded and non-brand, so every `shareOfVoice` comes back null and only the counts are published. Pass `queryClass=non-brand` (or `branded`) for a ratio, exactly as visibility-stats does. A project with no brand name or alias cannot be split by class, so a class-scoped read on one is refused rather than answered empty. Optional groupBy: model adds provider/requested-model groups with separate served-model evidence and sample counts. Optional model filters one exact requested model ID and requires provider. Unknown historical models remain explicit. Groups are not a matched-query or equal-weight comparison. Advanced reads support one market group or explicit scope: all-markets. No provider, discovery, or classifier work runs. Ranked lists are capped at 100 observed/other-source rows per group; pins remain complete. Model groups are capped at 50 and disclose truncation.",
9513
+ access: "read",
9514
+ tier: "monitoring",
9515
+ inputSchema: competitorLandscapeInputSchema,
9516
+ annotations: readAnnotations(),
9517
+ openApiOperations: ["GET /api/v1/projects/{name}/analytics/competitors"],
9518
+ handler: (client2, input) => {
9519
+ const { project, ...query } = input;
9520
+ return client2.getCompetitorLandscape(project, query);
9521
+ }
9522
+ }),
9481
9523
  defineTool({
9482
9524
  name: "canonry_search",
9483
9525
  title: "Search project (composite)",
@@ -5,11 +5,11 @@ import {
5
5
  installSkills,
6
6
  loadConfigRaw,
7
7
  saveConfigPatch
8
- } from "./chunk-CWXPBQR5.js";
8
+ } from "./chunk-FVC7PGYQ.js";
9
9
  import {
10
10
  SKILL_MANIFEST_FILENAME,
11
11
  SkillsClients
12
- } from "./chunk-RXXZ5BU5.js";
12
+ } from "./chunk-DY5ZDKQW.js";
13
13
 
14
14
  // src/skills-autosync.ts
15
15
  import fs from "fs";