@ainyc/canonry 3.2.5 → 3.3.2
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.
- package/assets/agent-workspace/skills/aero/SKILL.md +16 -4
- package/assets/agent-workspace/skills/canonry-setup/SKILL.md +30 -149
- package/assets/agent-workspace/skills/canonry-setup/references/aeo-analysis.md +15 -0
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +29 -11
- package/assets/assets/{index-B8epngdo.js → index-C1kUp1aS.js} +107 -107
- package/assets/assets/{index-CQGbgHZY.css → index-JG7aBJrz.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-PS7JRDL3.js → chunk-ALMP3NBQ.js} +136 -67
- package/dist/{chunk-AQ3AS25Y.js → chunk-HQ47AA6H.js} +72 -42
- package/dist/cli.js +531 -60
- package/dist/index.js +2 -2
- package/dist/mcp.js +1 -1
- package/package.json +7 -7
package/assets/index.html
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
16
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-C1kUp1aS.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-JG7aBJrz.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -641,6 +641,64 @@ function normalizeProjectDomain(input) {
|
|
|
641
641
|
}
|
|
642
642
|
return domain.replace(/^www\./, "");
|
|
643
643
|
}
|
|
644
|
+
var MULTI_LABEL_PUBLIC_SUFFIXES = /* @__PURE__ */ new Set([
|
|
645
|
+
"ac.uk",
|
|
646
|
+
"co.id",
|
|
647
|
+
"co.il",
|
|
648
|
+
"co.in",
|
|
649
|
+
"co.jp",
|
|
650
|
+
"co.kr",
|
|
651
|
+
"co.nz",
|
|
652
|
+
"co.th",
|
|
653
|
+
"co.uk",
|
|
654
|
+
"co.za",
|
|
655
|
+
"com.au",
|
|
656
|
+
"com.br",
|
|
657
|
+
"com.cn",
|
|
658
|
+
"com.mx",
|
|
659
|
+
"com.ph",
|
|
660
|
+
"com.sg",
|
|
661
|
+
"com.tr",
|
|
662
|
+
"edu.au",
|
|
663
|
+
"edu.sg",
|
|
664
|
+
"gov.au",
|
|
665
|
+
"gov.uk",
|
|
666
|
+
"me.uk",
|
|
667
|
+
"ne.jp",
|
|
668
|
+
"net.au",
|
|
669
|
+
"net.br",
|
|
670
|
+
"net.cn",
|
|
671
|
+
"net.in",
|
|
672
|
+
"net.tr",
|
|
673
|
+
"or.jp",
|
|
674
|
+
"or.kr",
|
|
675
|
+
"org.au",
|
|
676
|
+
"org.br",
|
|
677
|
+
"org.in",
|
|
678
|
+
"org.nz",
|
|
679
|
+
"org.tr",
|
|
680
|
+
"org.uk",
|
|
681
|
+
"org.za"
|
|
682
|
+
]);
|
|
683
|
+
function registrableDomain(input) {
|
|
684
|
+
const normalized = normalizeProjectDomain(input);
|
|
685
|
+
if (!normalized) return "";
|
|
686
|
+
const hostname = normalized.split("/")[0]?.split(":")[0] ?? "";
|
|
687
|
+
if (!hostname) return "";
|
|
688
|
+
const labels = hostname.split(".").filter(Boolean);
|
|
689
|
+
if (labels.length < 2) return "";
|
|
690
|
+
if (labels.length === 2) return labels.join(".");
|
|
691
|
+
const lastTwo = labels.slice(-2).join(".");
|
|
692
|
+
if (MULTI_LABEL_PUBLIC_SUFFIXES.has(lastTwo)) {
|
|
693
|
+
return labels.length >= 3 ? labels.slice(-3).join(".") : "";
|
|
694
|
+
}
|
|
695
|
+
return labels.slice(-2).join(".");
|
|
696
|
+
}
|
|
697
|
+
function brandLabelFromDomain(input) {
|
|
698
|
+
const reg = registrableDomain(input);
|
|
699
|
+
if (!reg) return "";
|
|
700
|
+
return reg.split(".")[0] ?? "";
|
|
701
|
+
}
|
|
644
702
|
function effectiveDomains(project) {
|
|
645
703
|
const all = [project.canonicalDomain, ...project.ownedDomains ?? []];
|
|
646
704
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1482,11 +1540,10 @@ function collectDistinctiveTokens(displayName, domains) {
|
|
|
1482
1540
|
tokens.add(token);
|
|
1483
1541
|
}
|
|
1484
1542
|
for (const domain of domains) {
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
}
|
|
1543
|
+
const reg = registrableDomain(domain);
|
|
1544
|
+
const brand = reg ? brandLabelFromDomain(reg) : normalizeProjectDomain(domain).split("/")[0]?.split(".")[0] ?? "";
|
|
1545
|
+
const token = brand.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
1546
|
+
if (isDistinctiveToken(token)) tokens.add(token);
|
|
1490
1547
|
}
|
|
1491
1548
|
return [...tokens];
|
|
1492
1549
|
}
|
|
@@ -2010,6 +2067,13 @@ function citationStateToCited(state) {
|
|
|
2010
2067
|
return state === "cited";
|
|
2011
2068
|
}
|
|
2012
2069
|
|
|
2070
|
+
// ../contracts/src/skills.ts
|
|
2071
|
+
import { z as z19 } from "zod";
|
|
2072
|
+
var codingAgentSchema = z19.enum(["claude", "codex"]);
|
|
2073
|
+
var CodingAgents = codingAgentSchema.enum;
|
|
2074
|
+
var skillsClientSchema = z19.enum(["claude", "codex", "all"]);
|
|
2075
|
+
var SkillsClients = skillsClientSchema.enum;
|
|
2076
|
+
|
|
2013
2077
|
// src/client.ts
|
|
2014
2078
|
function createApiClient() {
|
|
2015
2079
|
const config = loadConfig();
|
|
@@ -2677,21 +2741,21 @@ var ApiClient = class {
|
|
|
2677
2741
|
};
|
|
2678
2742
|
|
|
2679
2743
|
// src/mcp/tool-registry.ts
|
|
2680
|
-
import { z as
|
|
2744
|
+
import { z as z21 } from "zod";
|
|
2681
2745
|
|
|
2682
2746
|
// src/mcp/schema.ts
|
|
2683
|
-
import { z as
|
|
2684
|
-
var projectNameSchema =
|
|
2685
|
-
var runIdSchema =
|
|
2686
|
-
var insightIdSchema =
|
|
2687
|
-
var analyticsWindowSchema =
|
|
2688
|
-
var emptyInputSchema =
|
|
2689
|
-
var projectInputSchema =
|
|
2747
|
+
import { z as z20 } from "zod";
|
|
2748
|
+
var projectNameSchema = z20.string().min(1).describe("Canonry project name.");
|
|
2749
|
+
var runIdSchema = z20.string().min(1).describe("Canonry run ID.");
|
|
2750
|
+
var insightIdSchema = z20.string().min(1).describe("Canonry insight ID.");
|
|
2751
|
+
var analyticsWindowSchema = z20.enum(["7d", "30d", "90d", "all"]).describe("Analytics time window.");
|
|
2752
|
+
var emptyInputSchema = z20.object({});
|
|
2753
|
+
var projectInputSchema = z20.object({
|
|
2690
2754
|
project: projectNameSchema
|
|
2691
2755
|
});
|
|
2692
2756
|
function toJsonSchema(schema, name) {
|
|
2693
2757
|
return {
|
|
2694
|
-
...
|
|
2758
|
+
...z20.toJSONSchema(schema, { target: "draft-7" }),
|
|
2695
2759
|
title: name
|
|
2696
2760
|
};
|
|
2697
2761
|
}
|
|
@@ -2733,119 +2797,119 @@ function defineTool(tool) {
|
|
|
2733
2797
|
inputJsonSchema: toJsonSchema(tool.inputSchema, tool.name)
|
|
2734
2798
|
};
|
|
2735
2799
|
}
|
|
2736
|
-
var runTriggerInputSchema =
|
|
2800
|
+
var runTriggerInputSchema = z21.object({
|
|
2737
2801
|
project: projectNameSchema,
|
|
2738
2802
|
request: runTriggerRequestSchema.optional()
|
|
2739
2803
|
});
|
|
2740
|
-
var runsListInputSchema =
|
|
2804
|
+
var runsListInputSchema = z21.object({
|
|
2741
2805
|
project: projectNameSchema,
|
|
2742
|
-
limit:
|
|
2806
|
+
limit: z21.number().int().positive().max(500).optional()
|
|
2743
2807
|
});
|
|
2744
|
-
var runGetInputSchema =
|
|
2808
|
+
var runGetInputSchema = z21.object({
|
|
2745
2809
|
runId: runIdSchema
|
|
2746
2810
|
});
|
|
2747
|
-
var timelineInputSchema =
|
|
2811
|
+
var timelineInputSchema = z21.object({
|
|
2748
2812
|
project: projectNameSchema,
|
|
2749
|
-
location:
|
|
2813
|
+
location: z21.string().optional().describe("Location label. Use an empty string for locationless results.")
|
|
2750
2814
|
});
|
|
2751
|
-
var snapshotsListInputSchema =
|
|
2815
|
+
var snapshotsListInputSchema = z21.object({
|
|
2752
2816
|
project: projectNameSchema,
|
|
2753
|
-
limit:
|
|
2754
|
-
offset:
|
|
2755
|
-
location:
|
|
2817
|
+
limit: z21.number().int().positive().max(500).optional(),
|
|
2818
|
+
offset: z21.number().int().nonnegative().optional(),
|
|
2819
|
+
location: z21.string().optional().describe("Location label. Use an empty string for locationless results.")
|
|
2756
2820
|
});
|
|
2757
|
-
var snapshotsDiffInputSchema =
|
|
2821
|
+
var snapshotsDiffInputSchema = z21.object({
|
|
2758
2822
|
project: projectNameSchema,
|
|
2759
2823
|
run1: runIdSchema,
|
|
2760
2824
|
run2: runIdSchema
|
|
2761
2825
|
});
|
|
2762
|
-
var insightsListInputSchema =
|
|
2826
|
+
var insightsListInputSchema = z21.object({
|
|
2763
2827
|
project: projectNameSchema,
|
|
2764
|
-
dismissed:
|
|
2828
|
+
dismissed: z21.boolean().optional(),
|
|
2765
2829
|
runId: runIdSchema.optional()
|
|
2766
2830
|
});
|
|
2767
|
-
var insightInputSchema =
|
|
2831
|
+
var insightInputSchema = z21.object({
|
|
2768
2832
|
project: projectNameSchema,
|
|
2769
2833
|
insightId: insightIdSchema
|
|
2770
2834
|
});
|
|
2771
|
-
var healthHistoryInputSchema =
|
|
2835
|
+
var healthHistoryInputSchema = z21.object({
|
|
2772
2836
|
project: projectNameSchema,
|
|
2773
|
-
limit:
|
|
2837
|
+
limit: z21.number().int().positive().max(100).optional()
|
|
2774
2838
|
});
|
|
2775
|
-
var gscPerformanceInputSchema =
|
|
2839
|
+
var gscPerformanceInputSchema = z21.object({
|
|
2776
2840
|
project: projectNameSchema,
|
|
2777
|
-
startDate:
|
|
2778
|
-
endDate:
|
|
2779
|
-
query:
|
|
2780
|
-
page:
|
|
2781
|
-
limit:
|
|
2841
|
+
startDate: z21.string().optional(),
|
|
2842
|
+
endDate: z21.string().optional(),
|
|
2843
|
+
query: z21.string().optional(),
|
|
2844
|
+
page: z21.string().optional(),
|
|
2845
|
+
limit: z21.number().int().positive().max(500).optional(),
|
|
2782
2846
|
window: analyticsWindowSchema.optional()
|
|
2783
2847
|
});
|
|
2784
|
-
var gscInspectionsInputSchema =
|
|
2848
|
+
var gscInspectionsInputSchema = z21.object({
|
|
2785
2849
|
project: projectNameSchema,
|
|
2786
|
-
url:
|
|
2787
|
-
limit:
|
|
2850
|
+
url: z21.string().optional(),
|
|
2851
|
+
limit: z21.number().int().positive().max(500).optional()
|
|
2788
2852
|
});
|
|
2789
|
-
var gscCoverageHistoryInputSchema =
|
|
2853
|
+
var gscCoverageHistoryInputSchema = z21.object({
|
|
2790
2854
|
project: projectNameSchema,
|
|
2791
|
-
limit:
|
|
2855
|
+
limit: z21.number().int().positive().max(500).optional()
|
|
2792
2856
|
});
|
|
2793
|
-
var gaWindowInputSchema =
|
|
2857
|
+
var gaWindowInputSchema = z21.object({
|
|
2794
2858
|
project: projectNameSchema,
|
|
2795
2859
|
window: analyticsWindowSchema.optional()
|
|
2796
2860
|
});
|
|
2797
2861
|
var gaTrafficInputSchema = gaWindowInputSchema.extend({
|
|
2798
|
-
limit:
|
|
2862
|
+
limit: z21.number().int().positive().max(500).optional()
|
|
2799
2863
|
});
|
|
2800
|
-
var keywordsInputSchema =
|
|
2864
|
+
var keywordsInputSchema = z21.object({
|
|
2801
2865
|
project: projectNameSchema,
|
|
2802
2866
|
request: keywordBatchRequestSchema
|
|
2803
2867
|
});
|
|
2804
|
-
var keywordGenerateInputSchema =
|
|
2868
|
+
var keywordGenerateInputSchema = z21.object({
|
|
2805
2869
|
project: projectNameSchema,
|
|
2806
2870
|
request: keywordGenerateRequestSchema
|
|
2807
2871
|
});
|
|
2808
|
-
var competitorsInputSchema =
|
|
2872
|
+
var competitorsInputSchema = z21.object({
|
|
2809
2873
|
project: projectNameSchema,
|
|
2810
2874
|
request: competitorBatchRequestSchema
|
|
2811
2875
|
});
|
|
2812
|
-
var projectUpsertInputSchema =
|
|
2876
|
+
var projectUpsertInputSchema = z21.object({
|
|
2813
2877
|
project: projectNameSchema,
|
|
2814
2878
|
request: projectUpsertRequestSchema
|
|
2815
2879
|
});
|
|
2816
|
-
var applyConfigInputSchema =
|
|
2880
|
+
var applyConfigInputSchema = z21.object({
|
|
2817
2881
|
config: projectConfigSchema
|
|
2818
2882
|
});
|
|
2819
|
-
var scheduleSetInputSchema =
|
|
2883
|
+
var scheduleSetInputSchema = z21.object({
|
|
2820
2884
|
project: projectNameSchema,
|
|
2821
2885
|
schedule: scheduleUpsertRequestSchema
|
|
2822
2886
|
});
|
|
2823
|
-
var agentWebhookAttachInputSchema =
|
|
2887
|
+
var agentWebhookAttachInputSchema = z21.object({
|
|
2824
2888
|
project: projectNameSchema,
|
|
2825
|
-
url:
|
|
2889
|
+
url: z21.string().url()
|
|
2826
2890
|
});
|
|
2827
|
-
var doctorInputSchema =
|
|
2891
|
+
var doctorInputSchema = z21.object({
|
|
2828
2892
|
project: projectNameSchema.optional().describe("Project name to scope project-level checks. Omit to run global checks (provider keys, config, etc.)."),
|
|
2829
|
-
checks:
|
|
2893
|
+
checks: z21.array(z21.string().min(1)).optional().describe('Optional check IDs or wildcard prefixes (e.g. "google.auth.*", "config.providers"). Empty/omitted runs all matching checks for the chosen scope.')
|
|
2830
2894
|
});
|
|
2831
|
-
var contentTargetsInputSchema =
|
|
2895
|
+
var contentTargetsInputSchema = z21.object({
|
|
2832
2896
|
project: projectNameSchema,
|
|
2833
|
-
limit:
|
|
2834
|
-
includeInProgress:
|
|
2897
|
+
limit: z21.number().int().positive().max(500).optional().describe("Max rows. Defaults to all. Use a small number (3-10) when summarizing for the user."),
|
|
2898
|
+
includeInProgress: z21.boolean().optional().describe("Include rows that already have an in-flight tracked action. Default false.")
|
|
2835
2899
|
});
|
|
2836
|
-
var backlinksDomainsInputSchema =
|
|
2900
|
+
var backlinksDomainsInputSchema = z21.object({
|
|
2837
2901
|
project: projectNameSchema,
|
|
2838
|
-
limit:
|
|
2839
|
-
release:
|
|
2902
|
+
limit: z21.number().int().positive().max(200).optional().describe("Max linking-domain rows. Default 50, max 200."),
|
|
2903
|
+
release: z21.string().optional().describe("Common Crawl release id (e.g., cc-main-2026-jan-feb-mar). Omit for the most recent release with data.")
|
|
2840
2904
|
});
|
|
2841
|
-
var memoryUpsertInputSchema =
|
|
2905
|
+
var memoryUpsertInputSchema = z21.object({
|
|
2842
2906
|
project: projectNameSchema,
|
|
2843
|
-
key:
|
|
2844
|
-
value:
|
|
2907
|
+
key: z21.string().min(1).max(AGENT_MEMORY_KEY_MAX_LENGTH).describe(`Stable identifier for the note (max ${AGENT_MEMORY_KEY_MAX_LENGTH} chars). Writing the same key overwrites the prior value.`),
|
|
2908
|
+
value: z21.string().min(1).describe(`Plain-text note body (max ${AGENT_MEMORY_VALUE_MAX_BYTES} bytes). Use for durable operator preferences, migration context, or non-obvious reasoning that should survive future sessions.`)
|
|
2845
2909
|
});
|
|
2846
|
-
var memoryForgetInputSchema =
|
|
2910
|
+
var memoryForgetInputSchema = z21.object({
|
|
2847
2911
|
project: projectNameSchema,
|
|
2848
|
-
key:
|
|
2912
|
+
key: z21.string().min(1).max(AGENT_MEMORY_KEY_MAX_LENGTH).describe("Exact key of the note to remove. No-op (status=missing) when no note exists for that key.")
|
|
2849
2913
|
});
|
|
2850
2914
|
var AGENT_WEBHOOK_EVENTS = [
|
|
2851
2915
|
notificationEventSchema.enum["run.completed"],
|
|
@@ -2893,10 +2957,10 @@ var canonryMcpTools = [
|
|
|
2893
2957
|
description: "Search query snapshots and intelligence insights for the given text. Looks at snapshot answer text, cited domains, raw provider responses, and insight title/keyword/recommendation/cause. Returns ranked hits with snippets \u2014 use it instead of paginating snapshots when you need to find a competitor mention or term.",
|
|
2894
2958
|
access: "read",
|
|
2895
2959
|
tier: "core",
|
|
2896
|
-
inputSchema:
|
|
2960
|
+
inputSchema: z21.object({
|
|
2897
2961
|
project: projectNameSchema,
|
|
2898
|
-
q:
|
|
2899
|
-
limit:
|
|
2962
|
+
q: z21.string().min(2).describe("Search term, at least 2 characters."),
|
|
2963
|
+
limit: z21.number().int().positive().max(50).optional().describe("Max combined hits (1-50, default 25).")
|
|
2900
2964
|
}),
|
|
2901
2965
|
annotations: readAnnotations(),
|
|
2902
2966
|
openApiOperations: ["GET /api/v1/projects/{name}/search"],
|
|
@@ -3607,6 +3671,8 @@ export {
|
|
|
3607
3671
|
keywordGenerateRequestSchema,
|
|
3608
3672
|
competitorBatchRequestSchema,
|
|
3609
3673
|
normalizeProjectDomain,
|
|
3674
|
+
registrableDomain,
|
|
3675
|
+
brandLabelFromDomain,
|
|
3610
3676
|
effectiveDomains,
|
|
3611
3677
|
projectConfigSchema,
|
|
3612
3678
|
AppError,
|
|
@@ -3658,6 +3724,9 @@ export {
|
|
|
3658
3724
|
normalizeUrlPath,
|
|
3659
3725
|
emptyCitationVisibility,
|
|
3660
3726
|
citationStateToCited,
|
|
3727
|
+
CodingAgents,
|
|
3728
|
+
skillsClientSchema,
|
|
3729
|
+
SkillsClients,
|
|
3661
3730
|
createApiClient,
|
|
3662
3731
|
ApiClient,
|
|
3663
3732
|
canonryMcpTools
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
authInvalid,
|
|
20
20
|
authRequired,
|
|
21
21
|
brandKeyFromText,
|
|
22
|
+
brandLabelFromDomain,
|
|
22
23
|
buildRunErrorFromMessages,
|
|
23
24
|
canonryMcpTools,
|
|
24
25
|
categorizeSource,
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
projectConfigSchema,
|
|
50
51
|
projectUpsertRequestSchema,
|
|
51
52
|
providerError,
|
|
53
|
+
registrableDomain,
|
|
52
54
|
runInProgress,
|
|
53
55
|
runNotCancellable,
|
|
54
56
|
runTriggerRequestSchema,
|
|
@@ -62,7 +64,7 @@ import {
|
|
|
62
64
|
visibilityStateFromAnswerMentioned,
|
|
63
65
|
windowCutoff,
|
|
64
66
|
wordpressEnvSchema
|
|
65
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-ALMP3NBQ.js";
|
|
66
68
|
import {
|
|
67
69
|
IntelligenceService,
|
|
68
70
|
agentMemory,
|
|
@@ -721,6 +723,24 @@ async function keywordRoutes(app, opts) {
|
|
|
721
723
|
// ../api-routes/src/competitors.ts
|
|
722
724
|
import crypto6 from "crypto";
|
|
723
725
|
import { eq as eq5 } from "drizzle-orm";
|
|
726
|
+
function normalizeCompetitor(domain) {
|
|
727
|
+
const reg = registrableDomain(domain);
|
|
728
|
+
if (reg) return reg;
|
|
729
|
+
return normalizeProjectDomain(domain);
|
|
730
|
+
}
|
|
731
|
+
function normalizeCompetitorList(domains) {
|
|
732
|
+
const seen = /* @__PURE__ */ new Set();
|
|
733
|
+
const result = [];
|
|
734
|
+
for (const raw of domains) {
|
|
735
|
+
const trimmed = raw?.trim();
|
|
736
|
+
if (!trimmed) continue;
|
|
737
|
+
const normalized = normalizeCompetitor(trimmed);
|
|
738
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
739
|
+
seen.add(normalized);
|
|
740
|
+
result.push(normalized);
|
|
741
|
+
}
|
|
742
|
+
return result;
|
|
743
|
+
}
|
|
724
744
|
async function competitorRoutes(app) {
|
|
725
745
|
app.get("/projects/:name/competitors", async (request, reply) => {
|
|
726
746
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -734,9 +754,10 @@ async function competitorRoutes(app) {
|
|
|
734
754
|
throw validationError('Body must contain a "competitors" array');
|
|
735
755
|
}
|
|
736
756
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
757
|
+
const normalizedCompetitors = normalizeCompetitorList(body.competitors);
|
|
737
758
|
app.db.transaction((tx) => {
|
|
738
759
|
tx.delete(competitors).where(eq5(competitors.projectId, project.id)).run();
|
|
739
|
-
for (const domain of
|
|
760
|
+
for (const domain of normalizedCompetitors) {
|
|
740
761
|
tx.insert(competitors).values({
|
|
741
762
|
id: crypto6.randomUUID(),
|
|
742
763
|
projectId: project.id,
|
|
@@ -749,7 +770,7 @@ async function competitorRoutes(app) {
|
|
|
749
770
|
actor: "api",
|
|
750
771
|
action: "competitors.replaced",
|
|
751
772
|
entityType: "competitor",
|
|
752
|
-
diff: { competitors:
|
|
773
|
+
diff: { competitors: normalizedCompetitors }
|
|
753
774
|
});
|
|
754
775
|
});
|
|
755
776
|
const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
@@ -759,7 +780,7 @@ async function competitorRoutes(app) {
|
|
|
759
780
|
const project = resolveProject(app.db, request.params.name);
|
|
760
781
|
const body = parseCompetitorBatch(request.body);
|
|
761
782
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
762
|
-
const requested =
|
|
783
|
+
const requested = normalizeCompetitorList(body.competitors);
|
|
763
784
|
app.db.transaction((tx) => {
|
|
764
785
|
const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
765
786
|
const existingSet = new Set(existing.map((c) => c.domain));
|
|
@@ -789,7 +810,7 @@ async function competitorRoutes(app) {
|
|
|
789
810
|
app.delete("/projects/:name/competitors", async (request, reply) => {
|
|
790
811
|
const project = resolveProject(app.db, request.params.name);
|
|
791
812
|
const body = parseCompetitorBatch(request.body);
|
|
792
|
-
const requested = new Set(
|
|
813
|
+
const requested = new Set(normalizeCompetitorList(body.competitors));
|
|
793
814
|
app.db.transaction((tx) => {
|
|
794
815
|
const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
795
816
|
const rowsToDelete = existing.filter((c) => requested.has(c.domain));
|
|
@@ -819,16 +840,6 @@ function parseCompetitorBatch(value) {
|
|
|
819
840
|
}))
|
|
820
841
|
});
|
|
821
842
|
}
|
|
822
|
-
function uniqueStrings(values) {
|
|
823
|
-
const seen = /* @__PURE__ */ new Set();
|
|
824
|
-
const result = [];
|
|
825
|
-
for (const value of values) {
|
|
826
|
-
if (seen.has(value)) continue;
|
|
827
|
-
seen.add(value);
|
|
828
|
-
result.push(value);
|
|
829
|
-
}
|
|
830
|
-
return result;
|
|
831
|
-
}
|
|
832
843
|
|
|
833
844
|
// ../api-routes/src/runs.ts
|
|
834
845
|
import crypto8 from "crypto";
|
|
@@ -1550,7 +1561,8 @@ async function applyRoutes(app, opts) {
|
|
|
1550
1561
|
diff: { keywords: config.spec.keywords }
|
|
1551
1562
|
});
|
|
1552
1563
|
tx.delete(competitors).where(eq8(competitors.projectId, projectId)).run();
|
|
1553
|
-
|
|
1564
|
+
const normalizedCompetitors = normalizeCompetitorList2(config.spec.competitors);
|
|
1565
|
+
for (const domain of normalizedCompetitors) {
|
|
1554
1566
|
tx.insert(competitors).values({
|
|
1555
1567
|
id: crypto10.randomUUID(),
|
|
1556
1568
|
projectId,
|
|
@@ -1563,7 +1575,7 @@ async function applyRoutes(app, opts) {
|
|
|
1563
1575
|
actor: "api",
|
|
1564
1576
|
action: "competitors.replaced",
|
|
1565
1577
|
entityType: "competitor",
|
|
1566
|
-
diff: { competitors:
|
|
1578
|
+
diff: { competitors: normalizedCompetitors }
|
|
1567
1579
|
});
|
|
1568
1580
|
if (resolvedSchedule) {
|
|
1569
1581
|
const existingSched = tx.select().from(schedules).where(eq8(schedules.projectId, projectId)).get();
|
|
@@ -1651,6 +1663,19 @@ async function applyRoutes(app, opts) {
|
|
|
1651
1663
|
});
|
|
1652
1664
|
});
|
|
1653
1665
|
}
|
|
1666
|
+
function normalizeCompetitorList2(domains) {
|
|
1667
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1668
|
+
const result = [];
|
|
1669
|
+
for (const raw of domains) {
|
|
1670
|
+
const trimmed = raw?.trim();
|
|
1671
|
+
if (!trimmed) continue;
|
|
1672
|
+
const normalized = registrableDomain(trimmed) || normalizeProjectDomain(trimmed);
|
|
1673
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
1674
|
+
seen.add(normalized);
|
|
1675
|
+
result.push(normalized);
|
|
1676
|
+
}
|
|
1677
|
+
return result;
|
|
1678
|
+
}
|
|
1654
1679
|
|
|
1655
1680
|
// ../api-routes/src/history.ts
|
|
1656
1681
|
import { eq as eq9, desc as desc2, inArray } from "drizzle-orm";
|
|
@@ -15134,14 +15159,17 @@ function computeCompetitorOverlap(normalized, competitorDomains) {
|
|
|
15134
15159
|
if (lowerAnswer.includes(cd.toLowerCase())) {
|
|
15135
15160
|
overlapSet.add(cd);
|
|
15136
15161
|
}
|
|
15137
|
-
const brand = cd
|
|
15138
|
-
if (brand
|
|
15162
|
+
const brand = brandLabelFromDomain(cd);
|
|
15163
|
+
if (brand.length >= 4 && new RegExp(`\\b${escapeRegExp5(brand)}\\b`, "i").test(lowerAnswer)) {
|
|
15139
15164
|
overlapSet.add(cd);
|
|
15140
15165
|
}
|
|
15141
15166
|
}
|
|
15142
15167
|
}
|
|
15143
15168
|
return [...overlapSet];
|
|
15144
15169
|
}
|
|
15170
|
+
function escapeRegExp5(value) {
|
|
15171
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15172
|
+
}
|
|
15145
15173
|
function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, competitorDomains) {
|
|
15146
15174
|
if (!answerText || answerText.length < 20) return [];
|
|
15147
15175
|
const ownBrandKeys = new Set(
|
|
@@ -15211,15 +15239,17 @@ function cleanCandidateName(candidate) {
|
|
|
15211
15239
|
return candidate.replace(/^[\s"'`]+|[\s"'`.,:;!?]+$/g, "").replace(/\s+/g, " ").trim();
|
|
15212
15240
|
}
|
|
15213
15241
|
function collectBrandKeysFromDomain(domain) {
|
|
15214
|
-
const
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
for (const label of labels) {
|
|
15220
|
-
const key = label.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
15221
|
-
if (key.length >= 4) keys.add(key);
|
|
15242
|
+
const reg = registrableDomain(domain);
|
|
15243
|
+
if (!reg) {
|
|
15244
|
+
const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
|
|
15245
|
+
const fallback = hostname.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
15246
|
+
return fallback.length >= 4 ? [fallback] : [];
|
|
15222
15247
|
}
|
|
15248
|
+
const keys = /* @__PURE__ */ new Set();
|
|
15249
|
+
const fullKey = reg.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
15250
|
+
if (fullKey.length >= 4) keys.add(fullKey);
|
|
15251
|
+
const brand = brandLabelFromDomain(reg).replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
15252
|
+
if (brand.length >= 4) keys.add(brand);
|
|
15223
15253
|
return [...keys];
|
|
15224
15254
|
}
|
|
15225
15255
|
function matchesBrandKey(candidateKey, brandKeys) {
|
|
@@ -18398,7 +18428,7 @@ var SnapshotService = class {
|
|
|
18398
18428
|
});
|
|
18399
18429
|
const enrichedResults = applyBatchAssessment(queryResults, batchAssessment);
|
|
18400
18430
|
const summary = buildSnapshotSummary(companyName, profile.phrases, providers, enrichedResults, audit, batchAssessment);
|
|
18401
|
-
const reportCompetitors =
|
|
18431
|
+
const reportCompetitors = uniqueStrings([
|
|
18402
18432
|
...manualCompetitors,
|
|
18403
18433
|
...summary.topCompetitors.map((entry) => entry.name)
|
|
18404
18434
|
]);
|
|
@@ -18451,8 +18481,8 @@ var SnapshotService = class {
|
|
|
18451
18481
|
return {
|
|
18452
18482
|
industry: parsed.industry?.trim() || "Unknown",
|
|
18453
18483
|
summary: parsed.summary?.trim() || ctx.audit.summary,
|
|
18454
|
-
services:
|
|
18455
|
-
categoryTerms:
|
|
18484
|
+
services: uniqueStrings(parsed.services ?? []).slice(0, 6),
|
|
18485
|
+
categoryTerms: uniqueStrings(parsed.categoryTerms ?? []).slice(0, 8),
|
|
18456
18486
|
phrases: parsedPhrases
|
|
18457
18487
|
};
|
|
18458
18488
|
} catch (err) {
|
|
@@ -18520,9 +18550,9 @@ var SnapshotService = class {
|
|
|
18520
18550
|
accuracyNotes: null,
|
|
18521
18551
|
incorrectClaims: [],
|
|
18522
18552
|
recommendedCompetitors: preliminaryCompetitors,
|
|
18523
|
-
citedDomains:
|
|
18553
|
+
citedDomains: uniqueStrings(normalized.citedDomains),
|
|
18524
18554
|
groundingSources: normalized.groundingSources,
|
|
18525
|
-
searchQueries:
|
|
18555
|
+
searchQueries: uniqueStrings(normalized.searchQueries),
|
|
18526
18556
|
answerText: normalized.answerText,
|
|
18527
18557
|
error: null
|
|
18528
18558
|
};
|
|
@@ -18588,14 +18618,14 @@ var SnapshotService = class {
|
|
|
18588
18618
|
mentioned: assessment.mentioned,
|
|
18589
18619
|
describedAccurately: assessment.describedAccurately,
|
|
18590
18620
|
accuracyNotes: assessment.accuracyNotes ?? null,
|
|
18591
|
-
incorrectClaims:
|
|
18621
|
+
incorrectClaims: uniqueStrings(assessment.incorrectClaims ?? []).slice(0, 5),
|
|
18592
18622
|
...hasReviewedCompetitors ? {
|
|
18593
|
-
recommendedCompetitors:
|
|
18623
|
+
recommendedCompetitors: uniqueStrings(assessment.recommendedCompetitors ?? []).slice(0, 10)
|
|
18594
18624
|
} : {}
|
|
18595
18625
|
};
|
|
18596
18626
|
}),
|
|
18597
|
-
whatThisMeans:
|
|
18598
|
-
recommendedActions:
|
|
18627
|
+
whatThisMeans: uniqueStrings(parsed.whatThisMeans ?? []).slice(0, 4),
|
|
18628
|
+
recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
|
|
18599
18629
|
};
|
|
18600
18630
|
} catch (err) {
|
|
18601
18631
|
log12.warn("response.analysis-failed", {
|
|
@@ -18690,13 +18720,13 @@ function applyBatchAssessment(queryResults, batchAssessment) {
|
|
|
18690
18720
|
};
|
|
18691
18721
|
}
|
|
18692
18722
|
const reviewedCompetitors = assessment.recommendedCompetitors;
|
|
18693
|
-
const recommendedCompetitors = reviewedCompetitors !== void 0 ?
|
|
18723
|
+
const recommendedCompetitors = reviewedCompetitors !== void 0 ? uniqueStrings(reviewedCompetitors) : result.recommendedCompetitors;
|
|
18694
18724
|
return {
|
|
18695
18725
|
...result,
|
|
18696
18726
|
mentioned: result.mentioned || assessment.mentioned === true,
|
|
18697
18727
|
describedAccurately: assessment.describedAccurately ?? (result.mentioned ? "unknown" : "not-mentioned"),
|
|
18698
18728
|
accuracyNotes: assessment.accuracyNotes ?? result.accuracyNotes ?? null,
|
|
18699
|
-
incorrectClaims:
|
|
18729
|
+
incorrectClaims: uniqueStrings([
|
|
18700
18730
|
...result.incorrectClaims,
|
|
18701
18731
|
...assessment.incorrectClaims ?? []
|
|
18702
18732
|
]),
|
|
@@ -18726,7 +18756,7 @@ function buildSnapshotSummary(companyName, phrases, providers, queryResults, aud
|
|
|
18726
18756
|
const whatThisMeans = batchAssessment.whatThisMeans.length > 0 ? batchAssessment.whatThisMeans : [
|
|
18727
18757
|
defaultMeaning
|
|
18728
18758
|
];
|
|
18729
|
-
const combinedWhatThisMeans =
|
|
18759
|
+
const combinedWhatThisMeans = uniqueStrings([...whatThisMeans, ...failureNote]).slice(0, 5);
|
|
18730
18760
|
const recommendedActions = batchAssessment.recommendedActions.length > 0 ? batchAssessment.recommendedActions : buildFallbackRecommendedActions(audit);
|
|
18731
18761
|
return {
|
|
18732
18762
|
totalQueries: phrases.length,
|
|
@@ -18769,7 +18799,7 @@ function buildFallbackRecommendedActions(audit) {
|
|
|
18769
18799
|
"Add machine-readable trust signals such as schema, FAQs, and llms.txt support.",
|
|
18770
18800
|
"Build comparison and proof content that makes the category fit unmistakable."
|
|
18771
18801
|
];
|
|
18772
|
-
return
|
|
18802
|
+
return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
|
|
18773
18803
|
}
|
|
18774
18804
|
function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
|
|
18775
18805
|
const normalizedTarget = extractHostname2(targetDomain);
|
|
@@ -18843,11 +18873,11 @@ function parseJsonObject(input) {
|
|
|
18843
18873
|
}
|
|
18844
18874
|
function normalizeStringList(values) {
|
|
18845
18875
|
const items = values.flatMap((value) => value.split(","));
|
|
18846
|
-
return
|
|
18876
|
+
return uniqueStrings(
|
|
18847
18877
|
items.map((value) => value.trim()).filter(Boolean)
|
|
18848
18878
|
);
|
|
18849
18879
|
}
|
|
18850
|
-
function
|
|
18880
|
+
function uniqueStrings(values) {
|
|
18851
18881
|
if (!Array.isArray(values)) return [];
|
|
18852
18882
|
return [...new Set(
|
|
18853
18883
|
values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
|