@ainyc/canonry 4.30.0 → 4.31.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.
- package/README.md +15 -13
- package/assets/agent-workspace/skills/aero/SKILL.md +2 -2
- package/assets/agent-workspace/skills/aero/references/aeo-discovery.md +26 -17
- package/assets/agent-workspace/skills/aero/references/memory-patterns.md +9 -9
- package/assets/agent-workspace/skills/aero/references/orchestration.md +6 -6
- package/assets/agent-workspace/skills/aero/references/reporting.md +3 -3
- package/assets/agent-workspace/skills/canonry/SKILL.md +5 -3
- package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +9 -9
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +203 -200
- package/assets/agent-workspace/skills/canonry/references/indexing.md +35 -35
- package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +18 -18
- package/assets/agent-workspace/skills/canonry/references/wordpress-integration.md +11 -11
- package/assets/assets/index-C4UBTDDS.js +302 -0
- package/assets/assets/{index-BnALDZI7.css → index-CNKAwZMB.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-NIAAHWRF.js → chunk-5STLZRGB.js} +5 -3
- package/dist/{chunk-7UO3EGDB.js → chunk-HTNC6AWN.js} +24 -2
- package/dist/{chunk-PTFVEYUX.js → chunk-PUTJHEVR.js} +130 -13
- package/dist/{chunk-4EDC2P3J.js → chunk-U3YKRV47.js} +1 -1
- package/dist/cli.js +55 -14
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-ASXADXLF.js → intelligence-service-CJONZ7ST.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +8 -7
- package/assets/assets/index-BYiZYtd9.js +0 -302
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-C4UBTDDS.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CNKAwZMB.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
DISCOVERY_MAX_PROBES_CAP,
|
|
5
5
|
competitorBatchRequestSchema,
|
|
6
6
|
discoveryBucketSchema,
|
|
7
|
+
discoveryCompetitorTypeSchema,
|
|
7
8
|
discoveryPromoteRequestSchema,
|
|
8
9
|
discoveryRunRequestSchema,
|
|
9
10
|
keywordBatchRequestSchema,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
trafficConnectCloudRunRequestSchema,
|
|
21
22
|
trafficConnectWordpressRequestSchema,
|
|
22
23
|
trafficEventKindSchema
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-HTNC6AWN.js";
|
|
24
25
|
|
|
25
26
|
// src/config.ts
|
|
26
27
|
import fs from "fs";
|
|
@@ -1287,7 +1288,8 @@ var discoveryPromoteInputSchema = z2.object({
|
|
|
1287
1288
|
request: discoveryPromoteRequestSchema.extend({
|
|
1288
1289
|
// Stronger descriptions for the LLM. The base Zod schema enforces the shape.
|
|
1289
1290
|
buckets: z2.array(discoveryBucketSchema).min(1).optional().describe("Which probe buckets to adopt into the tracked basket. Omitted promotes cited + aspirational; include wasted-surface explicitly for off-ICP competitor gaps."),
|
|
1290
|
-
includeCompetitors: z2.boolean().optional().describe("Whether to also merge recurring discovered competitor domains into the project. Defaults to true.")
|
|
1291
|
+
includeCompetitors: z2.boolean().optional().describe("Whether to also merge recurring discovered competitor domains into the project. Defaults to true."),
|
|
1292
|
+
competitorTypes: z2.array(discoveryCompetitorTypeSchema).min(1).optional().describe("Which classified competitor types to merge. Omitted promotes direct-competitor only; pass an explicit list to also adopt editorial-media channels or to recover legacy unknown entries. Ignored when includeCompetitors is false.")
|
|
1291
1293
|
}).optional()
|
|
1292
1294
|
});
|
|
1293
1295
|
var AGENT_WEBHOOK_EVENTS = [
|
|
@@ -2242,7 +2244,7 @@ var canonryMcpTools = [
|
|
|
2242
2244
|
defineTool({
|
|
2243
2245
|
name: "canonry_discover_promote",
|
|
2244
2246
|
title: "Promote discovery session",
|
|
2245
|
-
description: 'Adopt a completed discovery session\'s bucketed queries into the project\'s tracked basket, tagged with provenance "discovery:<sessionId>". By default, only cited + aspirational queries are promoted; include wasted-surface explicitly when off-ICP competitor gaps should also be tracked. Recurring discovered competitor domains are also merged by default. Add-only and idempotent: queries/domains already tracked are returned under `skipped`, never inserted twice. Only sessions with status "completed" can be promoted. Call canonry_discover_promote_preview first to inspect candidates.',
|
|
2247
|
+
description: 'Adopt a completed discovery session\'s bucketed queries into the project\'s tracked basket, tagged with provenance "discovery:<sessionId>". By default, only cited + aspirational queries are promoted; include wasted-surface explicitly when off-ICP competitor gaps should also be tracked. Recurring discovered competitor domains classified as direct-competitor are also merged by default \u2014 pass request.competitorTypes to adopt editorial-media channels or recover legacy unknown entries. Add-only and idempotent: queries/domains already tracked are returned under `skipped`, never inserted twice. Only sessions with status "completed" can be promoted. Call canonry_discover_promote_preview first to inspect candidates.',
|
|
2246
2248
|
access: "write",
|
|
2247
2249
|
tier: "discovery",
|
|
2248
2250
|
inputSchema: discoveryPromoteInputSchema,
|
|
@@ -2382,11 +2382,29 @@ var DEFAULT_DISCOVERY_PROMOTE_BUCKETS = [
|
|
|
2382
2382
|
];
|
|
2383
2383
|
var DISCOVERY_PROMOTE_COMPETITOR_CAP = 20;
|
|
2384
2384
|
var DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS = 2;
|
|
2385
|
+
var discoveryCompetitorTypeSchema = z21.enum([
|
|
2386
|
+
"direct-competitor",
|
|
2387
|
+
"ota-aggregator",
|
|
2388
|
+
"editorial-media",
|
|
2389
|
+
"other",
|
|
2390
|
+
"unknown"
|
|
2391
|
+
]);
|
|
2392
|
+
var DiscoveryCompetitorTypes = discoveryCompetitorTypeSchema.enum;
|
|
2393
|
+
var DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES = [
|
|
2394
|
+
DiscoveryCompetitorTypes["direct-competitor"]
|
|
2395
|
+
];
|
|
2385
2396
|
var discoverySessionStatusSchema = z21.enum(["queued", "seeding", "probing", "completed", "failed"]);
|
|
2386
2397
|
var DiscoverySessionStatuses = discoverySessionStatusSchema.enum;
|
|
2387
2398
|
var discoveryCompetitorMapEntrySchema = z21.object({
|
|
2388
2399
|
domain: z21.string().min(1),
|
|
2389
|
-
hits: z21.number().int().positive()
|
|
2400
|
+
hits: z21.number().int().positive(),
|
|
2401
|
+
/**
|
|
2402
|
+
* Domain classification from the session's post-probe AI classification
|
|
2403
|
+
* pass. Defaults to `unknown` so competitor maps persisted before
|
|
2404
|
+
* classification existed (or by a session whose classification call failed)
|
|
2405
|
+
* still parse — those entries are excluded from the default promote filter.
|
|
2406
|
+
*/
|
|
2407
|
+
competitorType: discoveryCompetitorTypeSchema.default("unknown")
|
|
2390
2408
|
});
|
|
2391
2409
|
var discoveryProbeDtoSchema = z21.object({
|
|
2392
2410
|
id: z21.string(),
|
|
@@ -2428,7 +2446,8 @@ var discoveryRunRequestSchema = z21.object({
|
|
|
2428
2446
|
});
|
|
2429
2447
|
var discoveryPromoteRequestSchema = z21.object({
|
|
2430
2448
|
buckets: z21.array(discoveryBucketSchema).min(1).optional(),
|
|
2431
|
-
includeCompetitors: z21.boolean().optional()
|
|
2449
|
+
includeCompetitors: z21.boolean().optional(),
|
|
2450
|
+
competitorTypes: z21.array(discoveryCompetitorTypeSchema).min(1).optional()
|
|
2432
2451
|
});
|
|
2433
2452
|
var discoveryPromotePreviewSchema = z21.object({
|
|
2434
2453
|
sessionId: z21.string(),
|
|
@@ -2693,6 +2712,9 @@ export {
|
|
|
2693
2712
|
DEFAULT_DISCOVERY_PROMOTE_BUCKETS,
|
|
2694
2713
|
DISCOVERY_PROMOTE_COMPETITOR_CAP,
|
|
2695
2714
|
DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS,
|
|
2715
|
+
discoveryCompetitorTypeSchema,
|
|
2716
|
+
DiscoveryCompetitorTypes,
|
|
2717
|
+
DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES,
|
|
2696
2718
|
DiscoverySessionStatuses,
|
|
2697
2719
|
DISCOVERY_MAX_PROBES_CAP,
|
|
2698
2720
|
discoveryRunRequestSchema,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
loadConfigRaw,
|
|
7
7
|
saveConfigPatch
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5STLZRGB.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_RUN_HISTORY_LIMIT,
|
|
11
11
|
IntelligenceService,
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
schedules,
|
|
71
71
|
trafficSources,
|
|
72
72
|
usageCounters
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-U3YKRV47.js";
|
|
74
74
|
import {
|
|
75
75
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
76
76
|
AGENT_PROVIDER_IDS,
|
|
@@ -82,9 +82,11 @@ import {
|
|
|
82
82
|
CheckStatuses,
|
|
83
83
|
CitationStates,
|
|
84
84
|
DEFAULT_DISCOVERY_PROMOTE_BUCKETS,
|
|
85
|
+
DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES,
|
|
85
86
|
DISCOVERY_PROMOTE_COMPETITOR_CAP,
|
|
86
87
|
DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS,
|
|
87
88
|
DiscoveryBuckets,
|
|
89
|
+
DiscoveryCompetitorTypes,
|
|
88
90
|
DiscoverySessionStatuses,
|
|
89
91
|
MemorySources,
|
|
90
92
|
RunKinds,
|
|
@@ -175,7 +177,7 @@ import {
|
|
|
175
177
|
visibilityStateFromAnswerMentioned,
|
|
176
178
|
windowCutoff,
|
|
177
179
|
wordpressEnvSchema
|
|
178
|
-
} from "./chunk-
|
|
180
|
+
} from "./chunk-HTNC6AWN.js";
|
|
179
181
|
|
|
180
182
|
// src/telemetry.ts
|
|
181
183
|
import crypto from "crypto";
|
|
@@ -10425,7 +10427,7 @@ var routeCatalog = [
|
|
|
10425
10427
|
method: "post",
|
|
10426
10428
|
path: "/api/v1/projects/{name}/discover/sessions/{id}/promote",
|
|
10427
10429
|
summary: "Promote a discovery session into the tracked basket",
|
|
10428
|
-
description: 'Adopts a completed session\'s bucketed queries into the project\'s tracked basket, tagged with `provenance="discovery:<sessionId>"`. By default, only `cited` and `aspirational` queries are promoted; include `wasted-surface` explicitly when off-ICP competitor gaps should also be tracked. Recurring discovered competitor domains are also merged by default. Add-only and idempotent: queries/domains already tracked are returned under `skipped` rather than inserted twice. Only sessions with `status: "completed"` can be promoted.',
|
|
10430
|
+
description: 'Adopts a completed session\'s bucketed queries into the project\'s tracked basket, tagged with `provenance="discovery:<sessionId>"`. By default, only `cited` and `aspirational` queries are promoted; include `wasted-surface` explicitly when off-ICP competitor gaps should also be tracked. Recurring discovered competitor domains classified as `direct-competitor` are also merged by default \u2014 pass `competitorTypes` to adopt other classified types or to recover legacy `unknown` entries. Add-only and idempotent: queries/domains already tracked are returned under `skipped` rather than inserted twice. Only sessions with `status: "completed"` can be promoted.',
|
|
10429
10431
|
tags: ["discovery"],
|
|
10430
10432
|
parameters: [
|
|
10431
10433
|
nameParameter,
|
|
@@ -10446,6 +10448,14 @@ var routeCatalog = [
|
|
|
10446
10448
|
includeCompetitors: {
|
|
10447
10449
|
type: "boolean",
|
|
10448
10450
|
description: "Whether to also merge recurring discovered competitor domains. Defaults to true."
|
|
10451
|
+
},
|
|
10452
|
+
competitorTypes: {
|
|
10453
|
+
type: "array",
|
|
10454
|
+
items: {
|
|
10455
|
+
type: "string",
|
|
10456
|
+
enum: ["direct-competitor", "ota-aggregator", "editorial-media", "other", "unknown"]
|
|
10457
|
+
},
|
|
10458
|
+
description: "Which classified competitor types to merge. Omitted means direct-competitor only. Ignored when includeCompetitors is false."
|
|
10449
10459
|
}
|
|
10450
10460
|
}
|
|
10451
10461
|
}
|
|
@@ -19725,7 +19735,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
19725
19735
|
else if (bucket === DiscoveryBuckets.aspirational) aspirational.add(probe.query);
|
|
19726
19736
|
else if (bucket === DiscoveryBuckets["wasted-surface"]) wasted.add(probe.query);
|
|
19727
19737
|
}
|
|
19728
|
-
const competitorMap =
|
|
19738
|
+
const competitorMap = parseCompetitorMap(session.competitorMap);
|
|
19729
19739
|
const newCompetitors = selectEligibleCompetitors(competitorMap).filter((entry) => !seenCompetitors.has(entry.domain.toLowerCase()));
|
|
19730
19740
|
return reply.send({
|
|
19731
19741
|
sessionId: session.id,
|
|
@@ -19764,6 +19774,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
19764
19774
|
const buckets = parsed.data.buckets ?? DEFAULT_DISCOVERY_PROMOTE_BUCKETS;
|
|
19765
19775
|
const bucketSet = new Set(buckets);
|
|
19766
19776
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
19777
|
+
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
19767
19778
|
const probeRows = app.db.select().from(discoveryProbes).where(eq25(discoveryProbes.sessionId, session.id)).all();
|
|
19768
19779
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
19769
19780
|
for (const probe of probeRows) {
|
|
@@ -19790,8 +19801,8 @@ async function discoveryRoutes(app, opts) {
|
|
|
19790
19801
|
const existingCompetitors = new Set(
|
|
19791
19802
|
app.db.select({ domain: competitors.domain }).from(competitors).where(eq25(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
|
|
19792
19803
|
);
|
|
19793
|
-
const competitorMap =
|
|
19794
|
-
for (const entry of selectEligibleCompetitors(competitorMap)) {
|
|
19804
|
+
const competitorMap = parseCompetitorMap(session.competitorMap);
|
|
19805
|
+
for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
|
|
19795
19806
|
const key = entry.domain.toLowerCase();
|
|
19796
19807
|
if (existingCompetitors.has(key)) {
|
|
19797
19808
|
skippedCompetitors.push(entry.domain);
|
|
@@ -19856,7 +19867,7 @@ function serializeSession(row) {
|
|
|
19856
19867
|
citedCount: row.citedCount ?? null,
|
|
19857
19868
|
aspirationalCount: row.aspirationalCount ?? null,
|
|
19858
19869
|
wastedCount: row.wastedCount ?? null,
|
|
19859
|
-
competitorMap:
|
|
19870
|
+
competitorMap: parseCompetitorMap(row.competitorMap),
|
|
19860
19871
|
error: row.error ?? null,
|
|
19861
19872
|
startedAt: row.startedAt ?? null,
|
|
19862
19873
|
finishedAt: row.finishedAt ?? null,
|
|
@@ -19877,8 +19888,20 @@ function serializeProbe(row) {
|
|
|
19877
19888
|
createdAt: row.createdAt
|
|
19878
19889
|
};
|
|
19879
19890
|
}
|
|
19880
|
-
function
|
|
19881
|
-
|
|
19891
|
+
function parseCompetitorMap(json) {
|
|
19892
|
+
const raw = parseJsonColumn(
|
|
19893
|
+
json,
|
|
19894
|
+
[]
|
|
19895
|
+
);
|
|
19896
|
+
return raw.map((entry) => ({
|
|
19897
|
+
domain: entry.domain,
|
|
19898
|
+
hits: entry.hits,
|
|
19899
|
+
competitorType: entry.competitorType ?? DiscoveryCompetitorTypes.unknown
|
|
19900
|
+
}));
|
|
19901
|
+
}
|
|
19902
|
+
function selectEligibleCompetitors(competitorMap, competitorTypes) {
|
|
19903
|
+
const typeFilter = competitorTypes ? new Set(competitorTypes) : null;
|
|
19904
|
+
return competitorMap.filter((entry) => entry.hits >= DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS).filter((entry) => !typeFilter || typeFilter.has(entry.competitorType)).sort((a, b) => b.hits - a.hits || a.domain.localeCompare(b.domain)).slice(0, DISCOVERY_PROMOTE_COMPETITOR_CAP);
|
|
19882
19905
|
}
|
|
19883
19906
|
|
|
19884
19907
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
@@ -19895,7 +19918,7 @@ function classifyProbeBucket(input) {
|
|
|
19895
19918
|
if (competitorHit) return DiscoveryBuckets["wasted-surface"];
|
|
19896
19919
|
return DiscoveryBuckets.aspirational;
|
|
19897
19920
|
}
|
|
19898
|
-
function buildCompetitorMap(probes, project) {
|
|
19921
|
+
function buildCompetitorMap(probes, project, classification = {}) {
|
|
19899
19922
|
const canonical = new Set(project.canonicalDomains.map((d) => d.toLowerCase()));
|
|
19900
19923
|
const counts = /* @__PURE__ */ new Map();
|
|
19901
19924
|
for (const probe of probes) {
|
|
@@ -19908,7 +19931,19 @@ function buildCompetitorMap(probes, project) {
|
|
|
19908
19931
|
counts.set(domain, (counts.get(domain) ?? 0) + 1);
|
|
19909
19932
|
}
|
|
19910
19933
|
}
|
|
19911
|
-
return Array.from(counts.entries()).map(([domain, hits]) => ({
|
|
19934
|
+
return Array.from(counts.entries()).map(([domain, hits]) => ({
|
|
19935
|
+
domain,
|
|
19936
|
+
hits,
|
|
19937
|
+
competitorType: classification[domain] ?? DiscoveryCompetitorTypes.unknown
|
|
19938
|
+
})).sort((a, b) => b.hits - a.hits || a.domain.localeCompare(b.domain));
|
|
19939
|
+
}
|
|
19940
|
+
async function classifyCompetitorDomains(deps, project, icpDescription, domains) {
|
|
19941
|
+
if (domains.length === 0) return {};
|
|
19942
|
+
try {
|
|
19943
|
+
return await deps.classifyDomains({ project, icpDescription, domains });
|
|
19944
|
+
} catch {
|
|
19945
|
+
return {};
|
|
19946
|
+
}
|
|
19912
19947
|
}
|
|
19913
19948
|
async function pickCanonicals(candidates, deps, dedupThreshold) {
|
|
19914
19949
|
if (candidates.length === 0) return [];
|
|
@@ -19969,7 +20004,14 @@ async function executeDiscovery(opts) {
|
|
|
19969
20004
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19970
20005
|
}).run();
|
|
19971
20006
|
}
|
|
19972
|
-
const
|
|
20007
|
+
const domains = buildCompetitorMap(probeRows, opts.project).map((entry) => entry.domain);
|
|
20008
|
+
const classification = await classifyCompetitorDomains(
|
|
20009
|
+
opts.deps,
|
|
20010
|
+
opts.project,
|
|
20011
|
+
opts.icpDescription,
|
|
20012
|
+
domains
|
|
20013
|
+
);
|
|
20014
|
+
const competitorMap = buildCompetitorMap(probeRows, opts.project, classification);
|
|
19973
20015
|
opts.db.update(discoverySessions).set({
|
|
19974
20016
|
status: DiscoverySessionStatuses.completed,
|
|
19975
20017
|
probeCount: probedCanonicals.length,
|
|
@@ -24560,9 +24602,84 @@ function buildDefaultDeps(registry) {
|
|
|
24560
24602
|
citedDomains: normalized.citedDomains,
|
|
24561
24603
|
rawResponse: raw.rawResponse
|
|
24562
24604
|
};
|
|
24605
|
+
},
|
|
24606
|
+
async classifyDomains(input) {
|
|
24607
|
+
const prompt = buildClassificationPrompt(input);
|
|
24608
|
+
const text = await adapter.generateText(prompt, cfg);
|
|
24609
|
+
return parseClassificationResponse(text, input.domains);
|
|
24563
24610
|
}
|
|
24564
24611
|
};
|
|
24565
24612
|
}
|
|
24613
|
+
var CLASSIFICATION_CATEGORIES = [
|
|
24614
|
+
DiscoveryCompetitorTypes["direct-competitor"],
|
|
24615
|
+
DiscoveryCompetitorTypes["ota-aggregator"],
|
|
24616
|
+
DiscoveryCompetitorTypes["editorial-media"],
|
|
24617
|
+
DiscoveryCompetitorTypes.other
|
|
24618
|
+
];
|
|
24619
|
+
var CLASSIFICATION_CATEGORY_MATCHERS = CLASSIFICATION_CATEGORIES.map((category) => ({
|
|
24620
|
+
category,
|
|
24621
|
+
pattern: new RegExp(`(?<![a-z0-9])${category}(?![a-z0-9])`)
|
|
24622
|
+
}));
|
|
24623
|
+
function buildClassificationPrompt(input) {
|
|
24624
|
+
const tracked = input.project.competitorDomains.length > 0 ? input.project.competitorDomains.join(", ") : "none";
|
|
24625
|
+
return [
|
|
24626
|
+
"You are an AEO (Answer Engine Optimization) analyst classifying the domains that AI answer engines cited for a customer's tracked queries.",
|
|
24627
|
+
"",
|
|
24628
|
+
`Customer: ${input.project.name} (own domains: ${input.project.canonicalDomains.join(", ")})`,
|
|
24629
|
+
`ICP: ${input.icpDescription}`,
|
|
24630
|
+
`Already-tracked competitors: ${tracked}`,
|
|
24631
|
+
"",
|
|
24632
|
+
"Classify EACH domain below into exactly one category:",
|
|
24633
|
+
" - direct-competitor: a business competing directly with the customer for the same customers (another company in the same category). Every already-tracked competitor above is a direct-competitor.",
|
|
24634
|
+
" - ota-aggregator: online travel agencies, marketplaces, directories, booking platforms, or review aggregators that list many businesses (e.g. expedia.com, booking.com, tripadvisor.com, yelp.com, g2.com).",
|
|
24635
|
+
' - editorial-media: news sites, magazines, blogs, or "best of" listicle / round-up articles (e.g. timeout.com, nytimes.com, personal blogs).',
|
|
24636
|
+
" - other: anything else \u2014 government sites, social media, the customer itself, or domains unrelated to the competitive space.",
|
|
24637
|
+
"",
|
|
24638
|
+
"Domains:",
|
|
24639
|
+
...input.domains,
|
|
24640
|
+
"",
|
|
24641
|
+
"Return ONE line per domain in EXACTLY this format:",
|
|
24642
|
+
"<domain> => <category>",
|
|
24643
|
+
"",
|
|
24644
|
+
"Plain text only. No numbering, bullets, commentary, or markdown."
|
|
24645
|
+
].join("\n");
|
|
24646
|
+
}
|
|
24647
|
+
function parseClassificationResponse(text, domains) {
|
|
24648
|
+
const lines = text.split("\n").map((l) => l.trim().toLowerCase()).filter(Boolean);
|
|
24649
|
+
const result = {};
|
|
24650
|
+
for (const domain of domains) {
|
|
24651
|
+
const key = domain.toLowerCase();
|
|
24652
|
+
const line = lines.find((l) => startsWithDomainToken(l, key)) ?? lines.find((l) => containsDomainToken(l, key));
|
|
24653
|
+
if (!line) continue;
|
|
24654
|
+
const category = extractClassificationCategory(line);
|
|
24655
|
+
if (category) result[domain] = category;
|
|
24656
|
+
}
|
|
24657
|
+
return result;
|
|
24658
|
+
}
|
|
24659
|
+
function isDomainChar(ch) {
|
|
24660
|
+
return /[a-z0-9.-]/.test(ch);
|
|
24661
|
+
}
|
|
24662
|
+
function startsWithDomainToken(line, domain) {
|
|
24663
|
+
return line.startsWith(domain) && !isDomainChar(line[domain.length] ?? "");
|
|
24664
|
+
}
|
|
24665
|
+
function containsDomainToken(line, domain) {
|
|
24666
|
+
let idx = line.indexOf(domain);
|
|
24667
|
+
while (idx !== -1) {
|
|
24668
|
+
const before = line[idx - 1] ?? "";
|
|
24669
|
+
const after = line[idx + domain.length] ?? "";
|
|
24670
|
+
if (!isDomainChar(before) && !isDomainChar(after)) return true;
|
|
24671
|
+
idx = line.indexOf(domain, idx + 1);
|
|
24672
|
+
}
|
|
24673
|
+
return false;
|
|
24674
|
+
}
|
|
24675
|
+
function extractClassificationCategory(line) {
|
|
24676
|
+
const arrowIdx = line.indexOf("=>");
|
|
24677
|
+
const haystack = arrowIdx >= 0 ? line.slice(arrowIdx + 2) : line;
|
|
24678
|
+
for (const { category, pattern } of CLASSIFICATION_CATEGORY_MATCHERS) {
|
|
24679
|
+
if (pattern.test(haystack)) return category;
|
|
24680
|
+
}
|
|
24681
|
+
return null;
|
|
24682
|
+
}
|
|
24566
24683
|
function buildSeedPrompt(input) {
|
|
24567
24684
|
return [
|
|
24568
24685
|
"You are an AEO (Answer Engine Optimization) analyst expanding a tracked-query basket for a customer.",
|
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
setTelemetrySource,
|
|
21
21
|
showFirstRunNotice,
|
|
22
22
|
trackEvent
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-PUTJHEVR.js";
|
|
24
24
|
import {
|
|
25
25
|
CliError,
|
|
26
26
|
EXIT_SYSTEM_ERROR,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
saveConfig,
|
|
37
37
|
saveConfigPatch,
|
|
38
38
|
usageError
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-5STLZRGB.js";
|
|
40
40
|
import {
|
|
41
41
|
apiKeys,
|
|
42
42
|
competitors,
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
queries,
|
|
50
50
|
querySnapshots,
|
|
51
51
|
runs
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-U3YKRV47.js";
|
|
53
53
|
import {
|
|
54
54
|
CcReleaseSyncStatuses,
|
|
55
55
|
CheckScopes,
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
TrafficEventKinds,
|
|
64
64
|
determineAnswerMentioned,
|
|
65
65
|
discoveryBucketSchema,
|
|
66
|
+
discoveryCompetitorTypeSchema,
|
|
66
67
|
effectiveDomains,
|
|
67
68
|
formatRunErrorOneLine,
|
|
68
69
|
normalizeUrlPath,
|
|
@@ -70,7 +71,7 @@ import {
|
|
|
70
71
|
providerQuotaPolicySchema,
|
|
71
72
|
resolveProviderInput,
|
|
72
73
|
skillsClientSchema
|
|
73
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-HTNC6AWN.js";
|
|
74
75
|
|
|
75
76
|
// src/cli.ts
|
|
76
77
|
import { pathToFileURL } from "url";
|
|
@@ -622,7 +623,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
622
623
|
return result;
|
|
623
624
|
}
|
|
624
625
|
async function backfillInsightsCommand(project, opts) {
|
|
625
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
626
|
+
const { IntelligenceService } = await import("./intelligence-service-CJONZ7ST.js");
|
|
626
627
|
const config = loadConfig();
|
|
627
628
|
const db = createClient(config.database);
|
|
628
629
|
migrate(db);
|
|
@@ -2142,7 +2143,10 @@ async function discoverPromotePreview(project, sessionId, opts) {
|
|
|
2142
2143
|
for (const q of preview.queriesByBucket.aspirational.slice(0, 10)) console.log(` + ${q}`);
|
|
2143
2144
|
if (preview.suggestedCompetitors.length > 0) {
|
|
2144
2145
|
console.log(` Suggested new competitors:`);
|
|
2145
|
-
for (const c of preview.suggestedCompetitors)
|
|
2146
|
+
for (const c of preview.suggestedCompetitors) {
|
|
2147
|
+
console.log(` - ${c.domain} (${c.hits} hits, ${c.competitorType})`);
|
|
2148
|
+
}
|
|
2149
|
+
console.log(" Only direct-competitor is promoted by default \u2014 pass --competitor-types to include other types.");
|
|
2146
2150
|
}
|
|
2147
2151
|
console.log(`
|
|
2148
2152
|
Run \`canonry discover promote ${project} ${sessionId}\` to merge cited + aspirational queries.`);
|
|
@@ -2152,6 +2156,7 @@ async function discoverPromote(project, sessionId, opts) {
|
|
|
2152
2156
|
const client = getClient4();
|
|
2153
2157
|
const body = {};
|
|
2154
2158
|
if (opts.buckets && opts.buckets.length > 0) body.buckets = opts.buckets;
|
|
2159
|
+
if (opts.competitorTypes && opts.competitorTypes.length > 0) body.competitorTypes = opts.competitorTypes;
|
|
2155
2160
|
if (opts.includeCompetitors === false) body.includeCompetitors = false;
|
|
2156
2161
|
const result = await client.promoteDiscovery(project, sessionId, body);
|
|
2157
2162
|
if (opts.format === "json") {
|
|
@@ -2181,7 +2186,9 @@ function printSessionDetail(session) {
|
|
|
2181
2186
|
console.log(` Buckets: cited=${session.citedCount ?? 0} wasted-surface=${session.wastedCount ?? 0} aspirational=${session.aspirationalCount ?? 0}`);
|
|
2182
2187
|
if (session.competitorMap.length > 0) {
|
|
2183
2188
|
console.log(` Top recurring competitor domains:`);
|
|
2184
|
-
for (const c of session.competitorMap.slice(0, 10))
|
|
2189
|
+
for (const c of session.competitorMap.slice(0, 10)) {
|
|
2190
|
+
console.log(` - ${c.domain} (${c.hits} hits, ${c.competitorType})`);
|
|
2191
|
+
}
|
|
2185
2192
|
}
|
|
2186
2193
|
if (session.error) console.log(` Error: ${session.error}`);
|
|
2187
2194
|
if (session.startedAt) console.log(` Started: ${session.startedAt}`);
|
|
@@ -2265,6 +2272,38 @@ Usage: ${usage}`,
|
|
|
2265
2272
|
}
|
|
2266
2273
|
return buckets;
|
|
2267
2274
|
}
|
|
2275
|
+
var COMPETITOR_TYPE_VALUES = "direct-competitor, ota-aggregator, editorial-media, other, unknown";
|
|
2276
|
+
function parseCompetitorTypesOption(values, usage) {
|
|
2277
|
+
const raw = getStringArray(values, "competitor-types");
|
|
2278
|
+
if (!raw || raw.length === 0) return void 0;
|
|
2279
|
+
const expanded = raw.flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
2280
|
+
if (expanded.length === 0) {
|
|
2281
|
+
throw usageError(
|
|
2282
|
+
`Error: --competitor-types must include at least one value (valid: ${COMPETITOR_TYPE_VALUES})
|
|
2283
|
+
Usage: ${usage}`,
|
|
2284
|
+
{
|
|
2285
|
+
message: "--competitor-types must include at least one value",
|
|
2286
|
+
details: { command: "discover.promote", usage, option: "competitor-types", value: raw }
|
|
2287
|
+
}
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
const types = [];
|
|
2291
|
+
for (const value of expanded) {
|
|
2292
|
+
const parsed = discoveryCompetitorTypeSchema.safeParse(value);
|
|
2293
|
+
if (!parsed.success) {
|
|
2294
|
+
throw usageError(
|
|
2295
|
+
`Error: invalid --competitor-types value "${value}" (valid: ${COMPETITOR_TYPE_VALUES})
|
|
2296
|
+
Usage: ${usage}`,
|
|
2297
|
+
{
|
|
2298
|
+
message: `invalid --competitor-types value "${value}"`,
|
|
2299
|
+
details: { command: "discover.promote", usage, option: "competitor-types", value }
|
|
2300
|
+
}
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
types.push(parsed.data);
|
|
2304
|
+
}
|
|
2305
|
+
return types;
|
|
2306
|
+
}
|
|
2268
2307
|
var DISCOVER_CLI_COMMANDS = [
|
|
2269
2308
|
{
|
|
2270
2309
|
path: ["discover", "run"],
|
|
@@ -2378,13 +2417,14 @@ var DISCOVER_CLI_COMMANDS = [
|
|
|
2378
2417
|
},
|
|
2379
2418
|
{
|
|
2380
2419
|
path: ["discover", "promote"],
|
|
2381
|
-
usage: "canonry discover promote <project> <session-id> [--bucket cited,aspirational,wasted-surface] [--no-competitors] [--format json]",
|
|
2420
|
+
usage: "canonry discover promote <project> <session-id> [--bucket cited,aspirational,wasted-surface] [--competitor-types direct-competitor,editorial-media] [--no-competitors] [--format json]",
|
|
2382
2421
|
options: {
|
|
2383
2422
|
bucket: multiStringOption(),
|
|
2423
|
+
"competitor-types": multiStringOption(),
|
|
2384
2424
|
"no-competitors": { type: "boolean", default: false }
|
|
2385
2425
|
},
|
|
2386
2426
|
run: async (input) => {
|
|
2387
|
-
const usage = "canonry discover promote <project> <session-id> [--bucket cited,aspirational,wasted-surface] [--no-competitors] [--format json]";
|
|
2427
|
+
const usage = "canonry discover promote <project> <session-id> [--bucket cited,aspirational,wasted-surface] [--competitor-types direct-competitor,editorial-media] [--no-competitors] [--format json]";
|
|
2388
2428
|
const project = requireProject(input, "discover.promote", usage);
|
|
2389
2429
|
const sessionId = requirePositional(input, 1, {
|
|
2390
2430
|
command: "discover.promote",
|
|
@@ -2393,6 +2433,7 @@ var DISCOVER_CLI_COMMANDS = [
|
|
|
2393
2433
|
});
|
|
2394
2434
|
await discoverPromote(project, sessionId, {
|
|
2395
2435
|
buckets: parseBucketsOption(input.values, usage),
|
|
2436
|
+
competitorTypes: parseCompetitorTypesOption(input.values, usage),
|
|
2396
2437
|
includeCompetitors: !getBoolean(input.values, "no-competitors"),
|
|
2397
2438
|
format: input.format
|
|
2398
2439
|
});
|
|
@@ -11293,9 +11334,9 @@ var REGISTERED_CLI_COMMANDS = [
|
|
|
11293
11334
|
// src/cli.ts
|
|
11294
11335
|
import { createRequire as createRequire2 } from "module";
|
|
11295
11336
|
var USAGE3 = `
|
|
11296
|
-
|
|
11337
|
+
cnry \u2014 AEO monitoring CLI ('canonry' also works)
|
|
11297
11338
|
|
|
11298
|
-
Usage:
|
|
11339
|
+
Usage: cnry <command> [options]
|
|
11299
11340
|
|
|
11300
11341
|
Setup:
|
|
11301
11342
|
init Initialize config and database
|
|
@@ -11343,7 +11384,7 @@ Global options:
|
|
|
11343
11384
|
--help, -h Show help (use with any command group)
|
|
11344
11385
|
--version, -v Show version
|
|
11345
11386
|
|
|
11346
|
-
Run '
|
|
11387
|
+
Run 'cnry <command> --help' for details on a specific command.
|
|
11347
11388
|
`.trim();
|
|
11348
11389
|
var _require2 = createRequire2(import.meta.url);
|
|
11349
11390
|
var { version: VERSION } = _require2("../package.json");
|
|
@@ -11394,11 +11435,11 @@ async function runCli(args = process.argv.slice(2)) {
|
|
|
11394
11435
|
return 0;
|
|
11395
11436
|
}
|
|
11396
11437
|
throw usageError(`Error: unknown command: ${command}
|
|
11397
|
-
Run "
|
|
11438
|
+
Run "cnry --help" for usage.`, {
|
|
11398
11439
|
message: `unknown command: ${command}`,
|
|
11399
11440
|
details: {
|
|
11400
11441
|
command,
|
|
11401
|
-
usage: "
|
|
11442
|
+
usage: "cnry --help"
|
|
11402
11443
|
}
|
|
11403
11444
|
});
|
|
11404
11445
|
} catch (err) {
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PUTJHEVR.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-5STLZRGB.js";
|
|
7
|
+
import "./chunk-U3YKRV47.js";
|
|
8
|
+
import "./chunk-HTNC6AWN.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
CliError,
|
|
3
3
|
canonryMcpTools,
|
|
4
4
|
createApiClient
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-5STLZRGB.js";
|
|
6
|
+
import "./chunk-HTNC6AWN.js";
|
|
7
7
|
|
|
8
8
|
// src/mcp/cli.ts
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.31.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"canonry": "./bin/canonry.mjs",
|
|
18
|
+
"cnry": "./bin/canonry.mjs",
|
|
18
19
|
"canonry-mcp": "./bin/canonry-mcp.mjs"
|
|
19
20
|
},
|
|
20
21
|
"exports": {
|
|
@@ -62,20 +63,20 @@
|
|
|
62
63
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
63
64
|
"@ainyc/canonry-config": "0.0.0",
|
|
64
65
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-db": "0.0.0",
|
|
66
|
-
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
67
66
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
68
|
-
"@ainyc/canonry-integration-
|
|
67
|
+
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
68
|
+
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
69
|
+
"@ainyc/canonry-db": "0.0.0",
|
|
69
70
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
70
71
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
71
|
-
"@ainyc/canonry-integration-
|
|
72
|
+
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
72
73
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
73
|
-
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
74
74
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
75
75
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
76
76
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
77
|
+
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
77
78
|
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
78
|
-
"@ainyc/canonry-provider-
|
|
79
|
+
"@ainyc/canonry-provider-claude": "0.0.0"
|
|
79
80
|
},
|
|
80
81
|
"scripts": {
|
|
81
82
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|