@ainyc/canonry 4.55.1 → 4.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/assets/agent-workspace/skills/aero/SKILL.md +2 -0
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +2 -0
  3. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +55 -7
  4. package/assets/assets/{BacklinksPage-DVmaM864.js → BacklinksPage-yWx_BBLG.js} +1 -1
  5. package/assets/assets/ChartPrimitives-gqioJ07n.js +1 -0
  6. package/assets/assets/ProjectPage-D1tnXJ2L.js +6 -0
  7. package/assets/assets/{RunRow-BRqiLxj2.js → RunRow-DEr_yQLw.js} +1 -1
  8. package/assets/assets/{RunsPage-UxZ93-cg.js → RunsPage-DMrl5Fhn.js} +1 -1
  9. package/assets/assets/{SettingsPage-Cr5_EGbk.js → SettingsPage-Dp0TXf34.js} +1 -1
  10. package/assets/assets/{TrafficPage-CUC_lfTe.js → TrafficPage-POy4iHHt.js} +1 -1
  11. package/assets/assets/TrafficSourceDetailPage-BewjZ53n.js +1 -0
  12. package/assets/assets/{extract-error-message-DD5MibWI.js → extract-error-message-C0GGpK4T.js} +1 -1
  13. package/assets/assets/{index-nnF1LnyK.js → index-D0A-UvNH.js} +79 -79
  14. package/assets/assets/index-_jdnW4nh.css +1 -0
  15. package/assets/assets/{server-traffic-DjRISEZ-.js → server-traffic-B5rtrB-q.js} +1 -1
  16. package/assets/assets/{trash-2-CJ5M--Le.js → trash-2-CUczQ2Yl.js} +1 -1
  17. package/assets/index.html +2 -2
  18. package/dist/{chunk-UTM3FPAJ.js → chunk-4KWPOVIT.js} +181 -3
  19. package/dist/{chunk-ZY3EDW3S.js → chunk-6X5TF73A.js} +49 -3
  20. package/dist/{chunk-2OI7HFAB.js → chunk-I2LAM5IM.js} +309 -222
  21. package/dist/{chunk-OFY3Z2F7.js → chunk-WFVUZVJD.js} +368 -361
  22. package/dist/cli.js +66 -11
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-NKAEHHJ5.js → intelligence-service-NY3MAVPB.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +10 -10
  27. package/assets/assets/ChartPrimitives-9Kx3gzQL.js +0 -1
  28. package/assets/assets/ProjectPage-DtL3LFne.js +0 -6
  29. package/assets/assets/TrafficSourceDetailPage-DARPL2TU.js +0 -1
  30. package/assets/assets/index-Bm3JQsW0.css +0 -1
@@ -9,8 +9,10 @@ import {
9
9
  categorizeSourceWithCompetitors,
10
10
  categoryLabel,
11
11
  determineAnswerMentioned,
12
- normalizeProjectDomain
13
- } from "./chunk-OFY3Z2F7.js";
12
+ effectiveDomains,
13
+ normalizeProjectDomain,
14
+ registrableDomain
15
+ } from "./chunk-WFVUZVJD.js";
14
16
 
15
17
  // src/intelligence-service.ts
16
18
  import { eq, desc, asc, and, ne, or, inArray } from "drizzle-orm";
@@ -4156,6 +4158,172 @@ function createLogger(module) {
4156
4158
  };
4157
4159
  }
4158
4160
 
4161
+ // src/citation-utils.ts
4162
+ function domainMatches(domain, canonicalDomain) {
4163
+ const normalized = normalizeProjectDomain(canonicalDomain);
4164
+ const d = normalizeProjectDomain(domain);
4165
+ return d === normalized || d.endsWith(`.${normalized}`);
4166
+ }
4167
+ function pickProjectCitedDomain(citedDomains, projectDomains) {
4168
+ for (const cited of citedDomains) {
4169
+ if (projectDomains.some((pd) => domainMatches(cited, pd))) return cited;
4170
+ }
4171
+ return void 0;
4172
+ }
4173
+ function determineCitationState(normalized, domains) {
4174
+ for (const canonicalDomain of domains) {
4175
+ const bareDomain = normalizeProjectDomain(canonicalDomain);
4176
+ if (normalized.citedDomains.some((d) => domainMatches(d, bareDomain))) {
4177
+ return "cited";
4178
+ }
4179
+ const lowerDomain = bareDomain.toLowerCase();
4180
+ for (const source of normalized.groundingSources) {
4181
+ try {
4182
+ const uri = source.uri.toLowerCase();
4183
+ if (lowerDomain.includes(".") && uri.includes(lowerDomain)) {
4184
+ return "cited";
4185
+ }
4186
+ } catch {
4187
+ }
4188
+ if (source.title) {
4189
+ const titleLower = source.title.toLowerCase().replace(/^www\./, "");
4190
+ if (titleLower === lowerDomain || titleLower.endsWith(`.${lowerDomain}`)) {
4191
+ return "cited";
4192
+ }
4193
+ }
4194
+ }
4195
+ }
4196
+ return "not-cited";
4197
+ }
4198
+ function computeCompetitorOverlap(normalized, competitorDomains) {
4199
+ const overlapSet = /* @__PURE__ */ new Set();
4200
+ for (const d of normalized.citedDomains) {
4201
+ for (const cd of competitorDomains) {
4202
+ if (domainMatches(d, cd)) {
4203
+ overlapSet.add(cd);
4204
+ }
4205
+ }
4206
+ }
4207
+ for (const source of normalized.groundingSources) {
4208
+ const uri = source.uri.toLowerCase();
4209
+ for (const cd of competitorDomains) {
4210
+ if (uri.includes(cd.toLowerCase())) {
4211
+ overlapSet.add(cd);
4212
+ }
4213
+ }
4214
+ }
4215
+ if (normalized.answerText) {
4216
+ const lowerAnswer = normalized.answerText.toLowerCase();
4217
+ for (const cd of competitorDomains) {
4218
+ if (lowerAnswer.includes(cd.toLowerCase())) {
4219
+ overlapSet.add(cd);
4220
+ }
4221
+ const brand = brandLabelFromDomain(cd);
4222
+ if (brand.length >= 4 && new RegExp(`\\b${escapeRegExp(brand)}\\b`, "i").test(lowerAnswer)) {
4223
+ overlapSet.add(cd);
4224
+ }
4225
+ }
4226
+ }
4227
+ return [...overlapSet];
4228
+ }
4229
+ function escapeRegExp(value) {
4230
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4231
+ }
4232
+ function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, competitorDomains, ownBrandNames = []) {
4233
+ if (!answerText || answerText.length < 20) return [];
4234
+ const ownBrandKeys = new Set(
4235
+ ownDomains.flatMap((domain) => collectBrandKeysFromDomain(domain))
4236
+ );
4237
+ for (const name of ownBrandNames) {
4238
+ const key = brandKeyFromText(name);
4239
+ if (key.length >= 4) ownBrandKeys.add(key);
4240
+ }
4241
+ const knownCompetitorKeys = new Set(
4242
+ [...citedDomains, ...competitorDomains].flatMap((domain) => collectBrandKeysFromDomain(domain)).filter((key) => !ownBrandKeys.has(key))
4243
+ );
4244
+ if (knownCompetitorKeys.size === 0) return [];
4245
+ const candidatePatterns = [
4246
+ /^\s*(?:[-*]|\d+\.)\s+(?:\*\*)?([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50}?)(?:\*\*)?\s*[:\u2014\u2013-]/gm,
4247
+ /\*\*([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50})\*\*/g,
4248
+ /^#{1,4}\s+(?:\d+\.\s+)?(?:\*\*)?([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50}?)(?:\*\*)?$/gm,
4249
+ /\[([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50})\]\(https?:\/\/[^\s)]+\)/g
4250
+ ];
4251
+ const genericKeys = /* @__PURE__ */ new Set([
4252
+ "additional",
4253
+ "best",
4254
+ "benefits",
4255
+ "bottomline",
4256
+ "comparison",
4257
+ "conclusion",
4258
+ "directorylisting",
4259
+ "example",
4260
+ "expertise",
4261
+ "features",
4262
+ "finalthoughts",
4263
+ "howitworks",
4264
+ "important",
4265
+ "keybenefits",
4266
+ "keyfeatures",
4267
+ "major",
4268
+ "note",
4269
+ "notable",
4270
+ "option",
4271
+ "other",
4272
+ "overview",
4273
+ "pricing",
4274
+ "pros",
4275
+ "reviews",
4276
+ "step",
4277
+ "summary",
4278
+ "top",
4279
+ "verdict",
4280
+ "whattolookfor",
4281
+ "whyitmatters",
4282
+ "whyitstandsout",
4283
+ "whywechoseit"
4284
+ ]);
4285
+ const seen = /* @__PURE__ */ new Map();
4286
+ for (const pattern of candidatePatterns) {
4287
+ let match;
4288
+ while ((match = pattern.exec(answerText)) !== null) {
4289
+ const candidate = cleanCandidateName(match[1] ?? "");
4290
+ const candidateKey = brandKeyFromText(candidate);
4291
+ if (!candidateKey) continue;
4292
+ if (genericKeys.has(candidateKey)) continue;
4293
+ if (candidate.split(/\s+/).length > 6) continue;
4294
+ if (matchesBrandKey(candidateKey, ownBrandKeys)) continue;
4295
+ if (!matchesBrandKey(candidateKey, knownCompetitorKeys)) continue;
4296
+ if (!seen.has(candidateKey)) seen.set(candidateKey, candidate);
4297
+ }
4298
+ }
4299
+ return [...seen.values()].slice(0, 10);
4300
+ }
4301
+ function cleanCandidateName(candidate) {
4302
+ return candidate.replace(/^[\s"'`]+|[\s"'`.,:;!?]+$/g, "").replace(/\s+/g, " ").trim();
4303
+ }
4304
+ function collectBrandKeysFromDomain(domain) {
4305
+ const reg = registrableDomain(domain);
4306
+ if (!reg) {
4307
+ const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
4308
+ const fallback = hostname.replace(/[^a-z0-9]/gi, "").toLowerCase();
4309
+ return fallback.length >= 4 ? [fallback] : [];
4310
+ }
4311
+ const keys = /* @__PURE__ */ new Set();
4312
+ const fullKey = reg.replace(/[^a-z0-9]/gi, "").toLowerCase();
4313
+ if (fullKey.length >= 4) keys.add(fullKey);
4314
+ const brand = brandLabelFromDomain(reg).replace(/[^a-z0-9]/gi, "").toLowerCase();
4315
+ if (brand.length >= 4) keys.add(brand);
4316
+ return [...keys];
4317
+ }
4318
+ function matchesBrandKey(candidateKey, brandKeys) {
4319
+ for (const brandKey of brandKeys) {
4320
+ if (candidateKey === brandKey) return true;
4321
+ if (candidateKey.startsWith(brandKey) || candidateKey.endsWith(brandKey)) return true;
4322
+ if (brandKey.startsWith(candidateKey) || brandKey.endsWith(candidateKey)) return true;
4323
+ }
4324
+ return false;
4325
+ }
4326
+
4159
4327
  // src/intelligence-service.ts
4160
4328
  var RECURRENCE_LOOKBACK_RUNS = 5;
4161
4329
  var HISTORY_WINDOW_RUNS = Math.max(PERSISTENT_GAP_THRESHOLD, 5);
@@ -4537,6 +4705,11 @@ var IntelligenceService = class {
4537
4705
  });
4538
4706
  }
4539
4707
  buildRunData(runId, projectId, completedAt, location = null) {
4708
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq(projects.id, projectId)).get();
4709
+ const projectDomains = projectDomainRow ? effectiveDomains({
4710
+ canonicalDomain: projectDomainRow.canonicalDomain,
4711
+ ownedDomains: projectDomainRow.ownedDomains
4712
+ }) : [];
4540
4713
  const rows = this.db.select({
4541
4714
  query: queries.query,
4542
4715
  // Denormalized query text persisted by v58 — the fallback when the
@@ -4563,7 +4736,9 @@ var IntelligenceService = class {
4563
4736
  query: resolvedQuery,
4564
4737
  provider: r.provider,
4565
4738
  cited: r.citationState === CitationStates.cited,
4566
- citationUrl: domains[0] ?? void 0,
4739
+ // The project's OWN cited domain — never a co-cited competitor that
4740
+ // happens to sort first in the full citedDomains set.
4741
+ citationUrl: pickProjectCitedDomain(domains, projectDomains),
4567
4742
  // Snapshots carry their own location for downstream detectors. In
4568
4743
  // practice every snapshot in a single runId shares the run's
4569
4744
  // location; the per-row column is the same value duplicated, but
@@ -4629,6 +4804,9 @@ export {
4629
4804
  groupRunsByCreatedAt,
4630
4805
  pickGroupRepresentative,
4631
4806
  filterTrackedSnapshots,
4807
+ determineCitationState,
4808
+ computeCompetitorOverlap,
4809
+ extractRecommendedCompetitors,
4632
4810
  isBlogShapedQuery,
4633
4811
  buildInventory,
4634
4812
  buildContentTargetRows,
@@ -22,7 +22,7 @@ import {
22
22
  trafficConnectVercelRequestSchema,
23
23
  trafficConnectWordpressRequestSchema,
24
24
  trafficEventKindSchema
25
- } from "./chunk-OFY3Z2F7.js";
25
+ } from "./chunk-WFVUZVJD.js";
26
26
 
27
27
  // src/config.ts
28
28
  import fs from "fs";
@@ -3001,6 +3001,22 @@ var postApiV1ProjectsByNameTrafficSourcesByIdBackfill = (options) => {
3001
3001
  }
3002
3002
  });
3003
3003
  };
3004
+ var postApiV1ProjectsByNameTrafficSourcesByIdReset = (options) => {
3005
+ return (options.client ?? client).post({
3006
+ security: [
3007
+ {
3008
+ scheme: "bearer",
3009
+ type: "http"
3010
+ }
3011
+ ],
3012
+ url: "/api/v1/projects/{name}/traffic/sources/{id}/reset",
3013
+ ...options,
3014
+ headers: {
3015
+ "Content-Type": "application/json",
3016
+ ...options.headers
3017
+ }
3018
+ });
3019
+ };
3004
3020
  var getApiV1ProjectsByNameTrafficSources = (options) => {
3005
3021
  return (options.client ?? client).get({
3006
3022
  security: [
@@ -3564,9 +3580,14 @@ var ApiClient = class {
3564
3580
  })
3565
3581
  );
3566
3582
  }
3567
- async listRuns(project, limit) {
3583
+ async listRuns(project, limit, kind) {
3568
3584
  return this.invoke(
3569
- () => getApiV1ProjectsByNameRuns({ client: this.heyClient, path: { name: project }, query: { limit } })
3585
+ () => getApiV1ProjectsByNameRuns({
3586
+ client: this.heyClient,
3587
+ path: { name: project },
3588
+ // kind arrives as a free CLI string; the server validates it against the enum.
3589
+ query: { limit, kind }
3590
+ })
3570
3591
  );
3571
3592
  }
3572
3593
  async getLatestRun(project) {
@@ -4105,6 +4126,15 @@ var ApiClient = class {
4105
4126
  () => getApiV1ProjectsByNameTrafficSources({ client: this.heyClient, path: { name: project } })
4106
4127
  );
4107
4128
  }
4129
+ async trafficReset(project, sourceId) {
4130
+ return this.invoke(
4131
+ () => postApiV1ProjectsByNameTrafficSourcesByIdReset({
4132
+ client: this.heyClient,
4133
+ path: { name: project, id: sourceId },
4134
+ body: { advanceToNow: true }
4135
+ })
4136
+ );
4137
+ }
4108
4138
  async trafficStatus(project) {
4109
4139
  return this.invoke(
4110
4140
  () => getApiV1ProjectsByNameTrafficStatus({ client: this.heyClient, path: { name: project } })
@@ -4709,6 +4739,11 @@ var trafficBackfillInputSchema = z2.object({
4709
4739
  sourceId: z2.string().min(1).describe("Traffic source ID returned by canonry_traffic_sources_list."),
4710
4740
  days: z2.number().int().positive().max(30).optional().describe("Lookback window in days. Default 30, capped server-side at the upstream log retention ceiling (Cloud Logging _Default = 30d).")
4711
4741
  });
4742
+ var trafficResetInputSchema = z2.object({
4743
+ project: projectNameSchema,
4744
+ sourceId: z2.string().min(1).describe("Traffic source ID returned by canonry_traffic_sources_list."),
4745
+ advanceToNow: z2.literal(true).describe("Must be `true`. Explicit gate against accidental resets. Advances lastSyncedAt to NOW and clears the source's error state.")
4746
+ });
4712
4747
  var trafficEventsInputSchema = z2.object({
4713
4748
  project: projectNameSchema,
4714
4749
  since: z2.string().optional().describe("ISO 8601 lower bound. Defaults to 24h ago when omitted."),
@@ -5390,6 +5425,17 @@ var canonryMcpTools = [
5390
5425
  openApiOperations: ["POST /api/v1/projects/{name}/traffic/sources/{id}/backfill"],
5391
5426
  handler: (client2, input) => client2.trafficBackfill(input.project, input.sourceId, input.days !== void 0 ? { days: input.days } : void 0)
5392
5427
  }),
5428
+ defineTool({
5429
+ name: "canonry_traffic_reset",
5430
+ title: "Advance traffic source lastSyncedAt to NOW",
5431
+ description: "Operator recovery for a stuck traffic source. Advances `lastSyncedAt` to NOW, sets `status` back to `connected`, and clears `last_error`. Accepts any non-archived source: the `lastSyncedAt` advance determines the next sync window for time-windowed sources (Vercel, Cloud Run); cursor-based sources (WordPress) keep their `last_cursor` so the advance is informational. Common trigger: an idle Vercel/Cloud Run source whose `lastSyncedAt` aged past the upstream retention boundary and every sync now throws a retention error. Historical events in the gap are unrecoverable from the sync path; run canonry_traffic_backfill separately if any of them are needed. Archived sources are rejected \u2014 re-connect via the appropriate canonry_traffic_connect_* tool instead.",
5432
+ access: "write",
5433
+ tier: "traffic",
5434
+ inputSchema: trafficResetInputSchema,
5435
+ annotations: writeAnnotations({ idempotentHint: true, destructiveHint: true }),
5436
+ openApiOperations: ["POST /api/v1/projects/{name}/traffic/sources/{id}/reset"],
5437
+ handler: (client2, input) => client2.trafficReset(input.project, input.sourceId)
5438
+ }),
5393
5439
  defineTool({
5394
5440
  name: "canonry_project_upsert",
5395
5441
  title: "Create or replace project",