@ainyc/canonry 4.27.2 → 4.28.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/assets/agent-workspace/skills/aero/SKILL.md +1 -0
- package/assets/agent-workspace/skills/aero/references/aeo-discovery.md +49 -4
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +4 -2
- package/assets/assets/index--jYjUA0o.js +302 -0
- package/assets/assets/{index-rPok6yk8.css → index-BnALDZI7.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-2FAEQ56I.js → chunk-GB3QJURO.js} +32 -3
- package/dist/{chunk-ICWFH4JA.js → chunk-HONTKYY7.js} +152 -7
- package/dist/{chunk-HVW665A4.js → chunk-RLLFB3M3.js} +37 -0
- package/dist/{chunk-NXXD6TX7.js → chunk-UEV3HSRL.js} +1 -1
- package/dist/cli.js +81 -6
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-Z6QIELKP.js → intelligence-service-O6KB6YAM.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +9 -9
- package/assets/assets/index-BWjq1HP1.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--jYjUA0o.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BnALDZI7.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
4
4
|
DISCOVERY_MAX_PROBES_CAP,
|
|
5
5
|
competitorBatchRequestSchema,
|
|
6
|
+
discoveryBucketSchema,
|
|
7
|
+
discoveryPromoteRequestSchema,
|
|
6
8
|
discoveryRunRequestSchema,
|
|
7
9
|
keywordBatchRequestSchema,
|
|
8
10
|
keywordGenerateRequestSchema,
|
|
@@ -18,7 +20,7 @@ import {
|
|
|
18
20
|
trafficConnectCloudRunRequestSchema,
|
|
19
21
|
trafficConnectWordpressRequestSchema,
|
|
20
22
|
trafficEventKindSchema
|
|
21
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-RLLFB3M3.js";
|
|
22
24
|
|
|
23
25
|
// src/config.ts
|
|
24
26
|
import fs from "fs";
|
|
@@ -847,6 +849,13 @@ var ApiClient = class {
|
|
|
847
849
|
`/projects/${encodeURIComponent(project)}/discover/sessions/${encodeURIComponent(sessionId)}/promote`
|
|
848
850
|
);
|
|
849
851
|
}
|
|
852
|
+
async promoteDiscovery(project, sessionId, body) {
|
|
853
|
+
return this.request(
|
|
854
|
+
"POST",
|
|
855
|
+
`/projects/${encodeURIComponent(project)}/discover/sessions/${encodeURIComponent(sessionId)}/promote`,
|
|
856
|
+
body ?? {}
|
|
857
|
+
);
|
|
858
|
+
}
|
|
850
859
|
async wordpressConnect(project, body) {
|
|
851
860
|
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/connect`, body);
|
|
852
861
|
}
|
|
@@ -1272,6 +1281,15 @@ var discoverySessionIdInputSchema = z2.object({
|
|
|
1272
1281
|
project: projectNameSchema,
|
|
1273
1282
|
sessionId: z2.string().min(1).describe("Discovery session ID returned by canonry_discover_run_start.")
|
|
1274
1283
|
});
|
|
1284
|
+
var discoveryPromoteInputSchema = z2.object({
|
|
1285
|
+
project: projectNameSchema,
|
|
1286
|
+
sessionId: z2.string().min(1).describe("Discovery session ID returned by canonry_discover_run_start."),
|
|
1287
|
+
request: discoveryPromoteRequestSchema.extend({
|
|
1288
|
+
// Stronger descriptions for the LLM. The base Zod schema enforces the shape.
|
|
1289
|
+
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
|
+
}).optional()
|
|
1292
|
+
});
|
|
1275
1293
|
var AGENT_WEBHOOK_EVENTS = [
|
|
1276
1294
|
notificationEventSchema.enum["run.completed"],
|
|
1277
1295
|
notificationEventSchema.enum["insight.critical"],
|
|
@@ -2202,7 +2220,7 @@ var canonryMcpTools = [
|
|
|
2202
2220
|
defineTool({
|
|
2203
2221
|
name: "canonry_discover_session_get",
|
|
2204
2222
|
title: "Get discovery session",
|
|
2205
|
-
description: 'Get one discovery session with the full probe list (per-query bucket + cited domains). Use after canonry_discover_run_start to inspect what the discovery pipeline produced; this is the canonical read for "what did discovery find" before
|
|
2223
|
+
description: 'Get one discovery session with the full probe list (per-query bucket + cited domains). Use after canonry_discover_run_start to inspect what the discovery pipeline produced; this is the canonical read for "what did discovery find" before calling canonry_discover_promote.',
|
|
2206
2224
|
access: "read",
|
|
2207
2225
|
tier: "discovery",
|
|
2208
2226
|
inputSchema: discoverySessionIdInputSchema,
|
|
@@ -2213,13 +2231,24 @@ var canonryMcpTools = [
|
|
|
2213
2231
|
defineTool({
|
|
2214
2232
|
name: "canonry_discover_promote_preview",
|
|
2215
2233
|
title: "Preview discovery promotion",
|
|
2216
|
-
description: "Read-only preview of
|
|
2234
|
+
description: "Read-only preview of available promotion candidates for a session: bucketed query lists and recurring suggested competitor domains not already in the project's tracked competitor list. Use it to confirm a basket before calling canonry_discover_promote.",
|
|
2217
2235
|
access: "read",
|
|
2218
2236
|
tier: "discovery",
|
|
2219
2237
|
inputSchema: discoverySessionIdInputSchema,
|
|
2220
2238
|
annotations: readAnnotations(),
|
|
2221
2239
|
openApiOperations: ["GET /api/v1/projects/{name}/discover/sessions/{id}/promote"],
|
|
2222
2240
|
handler: (client, input) => client.previewDiscoveryPromote(input.project, input.sessionId)
|
|
2241
|
+
}),
|
|
2242
|
+
defineTool({
|
|
2243
|
+
name: "canonry_discover_promote",
|
|
2244
|
+
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.',
|
|
2246
|
+
access: "write",
|
|
2247
|
+
tier: "discovery",
|
|
2248
|
+
inputSchema: discoveryPromoteInputSchema,
|
|
2249
|
+
annotations: writeAnnotations({ idempotentHint: true }),
|
|
2250
|
+
openApiOperations: ["POST /api/v1/projects/{name}/discover/sessions/{id}/promote"],
|
|
2251
|
+
handler: (client, input) => client.promoteDiscovery(input.project, input.sessionId, input.request)
|
|
2223
2252
|
})
|
|
2224
2253
|
];
|
|
2225
2254
|
var CANONRY_MCP_TOOL_COUNT = canonryMcpTools.length;
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
loadConfigRaw,
|
|
7
7
|
saveConfigPatch
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-GB3QJURO.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-UEV3HSRL.js";
|
|
74
74
|
import {
|
|
75
75
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
76
76
|
AGENT_PROVIDER_IDS,
|
|
@@ -81,6 +81,9 @@ import {
|
|
|
81
81
|
CheckScopes,
|
|
82
82
|
CheckStatuses,
|
|
83
83
|
CitationStates,
|
|
84
|
+
DEFAULT_DISCOVERY_PROMOTE_BUCKETS,
|
|
85
|
+
DISCOVERY_PROMOTE_COMPETITOR_CAP,
|
|
86
|
+
DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS,
|
|
84
87
|
DiscoveryBuckets,
|
|
85
88
|
DiscoverySessionStatuses,
|
|
86
89
|
MemorySources,
|
|
@@ -119,6 +122,7 @@ import {
|
|
|
119
122
|
deltaTone,
|
|
120
123
|
determineAnswerMentioned,
|
|
121
124
|
discoveryBucketSchema,
|
|
125
|
+
discoveryPromoteRequestSchema,
|
|
122
126
|
discoveryRunRequestSchema,
|
|
123
127
|
effectiveDomains,
|
|
124
128
|
emptyCitationVisibility,
|
|
@@ -171,7 +175,7 @@ import {
|
|
|
171
175
|
visibilityStateFromAnswerMentioned,
|
|
172
176
|
windowCutoff,
|
|
173
177
|
wordpressEnvSchema
|
|
174
|
-
} from "./chunk-
|
|
178
|
+
} from "./chunk-RLLFB3M3.js";
|
|
175
179
|
|
|
176
180
|
// src/telemetry.ts
|
|
177
181
|
import crypto from "crypto";
|
|
@@ -10406,7 +10410,7 @@ var routeCatalog = [
|
|
|
10406
10410
|
method: "get",
|
|
10407
10411
|
path: "/api/v1/projects/{name}/discover/sessions/{id}/promote",
|
|
10408
10412
|
summary: "Preview a discovery promotion plan (read-only)",
|
|
10409
|
-
description: "Returns
|
|
10413
|
+
description: "Returns available promotion candidates: queries grouped by bucket, plus recurring suggested competitor domains not already tracked. Read-only \u2014 use the POST to actually adopt the default subset or an explicit bucket subset.",
|
|
10410
10414
|
tags: ["discovery"],
|
|
10411
10415
|
parameters: [
|
|
10412
10416
|
nameParameter,
|
|
@@ -10416,6 +10420,43 @@ var routeCatalog = [
|
|
|
10416
10420
|
200: { description: "Promote preview returned." },
|
|
10417
10421
|
404: { description: "Project or session not found." }
|
|
10418
10422
|
}
|
|
10423
|
+
},
|
|
10424
|
+
{
|
|
10425
|
+
method: "post",
|
|
10426
|
+
path: "/api/v1/projects/{name}/discover/sessions/{id}/promote",
|
|
10427
|
+
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.',
|
|
10429
|
+
tags: ["discovery"],
|
|
10430
|
+
parameters: [
|
|
10431
|
+
nameParameter,
|
|
10432
|
+
{ name: "id", in: "path", required: true, description: "Discovery session ID.", schema: stringSchema }
|
|
10433
|
+
],
|
|
10434
|
+
requestBody: {
|
|
10435
|
+
required: false,
|
|
10436
|
+
content: {
|
|
10437
|
+
"application/json": {
|
|
10438
|
+
schema: {
|
|
10439
|
+
type: "object",
|
|
10440
|
+
properties: {
|
|
10441
|
+
buckets: {
|
|
10442
|
+
type: "array",
|
|
10443
|
+
items: { type: "string", enum: ["cited", "aspirational", "wasted-surface"] },
|
|
10444
|
+
description: "Which probe buckets to promote. Omitted means cited + aspirational."
|
|
10445
|
+
},
|
|
10446
|
+
includeCompetitors: {
|
|
10447
|
+
type: "boolean",
|
|
10448
|
+
description: "Whether to also merge recurring discovered competitor domains. Defaults to true."
|
|
10449
|
+
}
|
|
10450
|
+
}
|
|
10451
|
+
}
|
|
10452
|
+
}
|
|
10453
|
+
}
|
|
10454
|
+
},
|
|
10455
|
+
responses: {
|
|
10456
|
+
200: { description: "Promotion applied; returns promoted + skipped query/competitor lists." },
|
|
10457
|
+
400: { description: "Session is not completed, or invalid request body." },
|
|
10458
|
+
404: { description: "Project or session not found." }
|
|
10459
|
+
}
|
|
10419
10460
|
}
|
|
10420
10461
|
];
|
|
10421
10462
|
var canonryLocalRouteCatalog = [
|
|
@@ -19685,7 +19726,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
19685
19726
|
else if (bucket === DiscoveryBuckets["wasted-surface"]) wasted.add(probe.query);
|
|
19686
19727
|
}
|
|
19687
19728
|
const competitorMap = parseJsonColumn(session.competitorMap, []);
|
|
19688
|
-
const newCompetitors = competitorMap.filter((entry) => !seenCompetitors.has(entry.domain.toLowerCase()))
|
|
19729
|
+
const newCompetitors = selectEligibleCompetitors(competitorMap).filter((entry) => !seenCompetitors.has(entry.domain.toLowerCase()));
|
|
19689
19730
|
return reply.send({
|
|
19690
19731
|
sessionId: session.id,
|
|
19691
19732
|
projectId: project.id,
|
|
@@ -19699,6 +19740,107 @@ async function discoveryRoutes(app, opts) {
|
|
|
19699
19740
|
});
|
|
19700
19741
|
}
|
|
19701
19742
|
);
|
|
19743
|
+
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
19744
|
+
const project = resolveProject(app.db, request.params.name);
|
|
19745
|
+
const session = app.db.select().from(discoverySessions).where(eq25(discoverySessions.id, request.params.id)).get();
|
|
19746
|
+
if (!session || session.projectId !== project.id) {
|
|
19747
|
+
throw notFound("Discovery session", request.params.id);
|
|
19748
|
+
}
|
|
19749
|
+
const parsed = discoveryPromoteRequestSchema.safeParse(request.body ?? {});
|
|
19750
|
+
if (!parsed.success) {
|
|
19751
|
+
throw validationError("Invalid discovery promote request", {
|
|
19752
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
19753
|
+
path: issue.path.join("."),
|
|
19754
|
+
message: issue.message
|
|
19755
|
+
}))
|
|
19756
|
+
});
|
|
19757
|
+
}
|
|
19758
|
+
if (session.status !== DiscoverySessionStatuses.completed) {
|
|
19759
|
+
throw validationError(
|
|
19760
|
+
`Discovery session is "${session.status}" \u2014 only completed sessions can be promoted.`,
|
|
19761
|
+
{ status: session.status }
|
|
19762
|
+
);
|
|
19763
|
+
}
|
|
19764
|
+
const buckets = parsed.data.buckets ?? DEFAULT_DISCOVERY_PROMOTE_BUCKETS;
|
|
19765
|
+
const bucketSet = new Set(buckets);
|
|
19766
|
+
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
19767
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq25(discoveryProbes.sessionId, session.id)).all();
|
|
19768
|
+
const candidateQueries = /* @__PURE__ */ new Set();
|
|
19769
|
+
for (const probe of probeRows) {
|
|
19770
|
+
if (!probe.bucket) continue;
|
|
19771
|
+
const bucket = discoveryBucketSchema.safeParse(probe.bucket);
|
|
19772
|
+
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
19773
|
+
}
|
|
19774
|
+
const existingQueries = new Set(
|
|
19775
|
+
app.db.select({ query: queries.query }).from(queries).where(eq25(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
19776
|
+
);
|
|
19777
|
+
const promotedQueries = [];
|
|
19778
|
+
const skippedQueries = [];
|
|
19779
|
+
for (const query of Array.from(candidateQueries).sort()) {
|
|
19780
|
+
if (existingQueries.has(query.toLowerCase())) {
|
|
19781
|
+
skippedQueries.push(query);
|
|
19782
|
+
} else {
|
|
19783
|
+
promotedQueries.push(query);
|
|
19784
|
+
existingQueries.add(query.toLowerCase());
|
|
19785
|
+
}
|
|
19786
|
+
}
|
|
19787
|
+
const promotedCompetitors = [];
|
|
19788
|
+
const skippedCompetitors = [];
|
|
19789
|
+
if (includeCompetitors) {
|
|
19790
|
+
const existingCompetitors = new Set(
|
|
19791
|
+
app.db.select({ domain: competitors.domain }).from(competitors).where(eq25(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
|
|
19792
|
+
);
|
|
19793
|
+
const competitorMap = parseJsonColumn(session.competitorMap, []);
|
|
19794
|
+
for (const entry of selectEligibleCompetitors(competitorMap)) {
|
|
19795
|
+
const key = entry.domain.toLowerCase();
|
|
19796
|
+
if (existingCompetitors.has(key)) {
|
|
19797
|
+
skippedCompetitors.push(entry.domain);
|
|
19798
|
+
} else {
|
|
19799
|
+
promotedCompetitors.push(entry.domain);
|
|
19800
|
+
existingCompetitors.add(key);
|
|
19801
|
+
}
|
|
19802
|
+
}
|
|
19803
|
+
}
|
|
19804
|
+
const provenance = `discovery:${session.id}`;
|
|
19805
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19806
|
+
if (promotedQueries.length > 0 || promotedCompetitors.length > 0) {
|
|
19807
|
+
app.db.transaction((tx) => {
|
|
19808
|
+
for (const query of promotedQueries) {
|
|
19809
|
+
tx.insert(queries).values({
|
|
19810
|
+
id: crypto21.randomUUID(),
|
|
19811
|
+
projectId: project.id,
|
|
19812
|
+
query,
|
|
19813
|
+
provenance,
|
|
19814
|
+
createdAt: now
|
|
19815
|
+
}).run();
|
|
19816
|
+
}
|
|
19817
|
+
for (const domain of promotedCompetitors) {
|
|
19818
|
+
tx.insert(competitors).values({
|
|
19819
|
+
id: crypto21.randomUUID(),
|
|
19820
|
+
projectId: project.id,
|
|
19821
|
+
domain,
|
|
19822
|
+
provenance,
|
|
19823
|
+
createdAt: now
|
|
19824
|
+
}).run();
|
|
19825
|
+
}
|
|
19826
|
+
writeAuditLog(tx, {
|
|
19827
|
+
projectId: project.id,
|
|
19828
|
+
actor: "api",
|
|
19829
|
+
action: "discovery.promoted",
|
|
19830
|
+
entityType: "discovery_session",
|
|
19831
|
+
entityId: session.id,
|
|
19832
|
+
diff: { queries: promotedQueries, competitors: promotedCompetitors }
|
|
19833
|
+
});
|
|
19834
|
+
});
|
|
19835
|
+
}
|
|
19836
|
+
const result = {
|
|
19837
|
+
sessionId: session.id,
|
|
19838
|
+
projectId: project.id,
|
|
19839
|
+
promoted: { queries: promotedQueries, competitors: promotedCompetitors },
|
|
19840
|
+
skipped: { queries: skippedQueries, competitors: skippedCompetitors }
|
|
19841
|
+
};
|
|
19842
|
+
return reply.send(result);
|
|
19843
|
+
});
|
|
19702
19844
|
}
|
|
19703
19845
|
function serializeSession(row) {
|
|
19704
19846
|
return {
|
|
@@ -19735,6 +19877,9 @@ function serializeProbe(row) {
|
|
|
19735
19877
|
createdAt: row.createdAt
|
|
19736
19878
|
};
|
|
19737
19879
|
}
|
|
19880
|
+
function selectEligibleCompetitors(competitorMap) {
|
|
19881
|
+
return competitorMap.filter((entry) => entry.hits >= DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS).sort((a, b) => b.hits - a.hits || a.domain.localeCompare(b.domain)).slice(0, DISCOVERY_PROMOTE_COMPETITOR_CAP);
|
|
19882
|
+
}
|
|
19738
19883
|
|
|
19739
19884
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
19740
19885
|
import crypto22 from "crypto";
|
|
@@ -24482,7 +24627,7 @@ function writeDiscoveryInsight(db, input) {
|
|
|
24482
24627
|
provider: input.seedProvider,
|
|
24483
24628
|
recommendation: JSON.stringify({
|
|
24484
24629
|
action: "review-discovered-basket",
|
|
24485
|
-
summary: `Run \`canonry discover show ${input.sessionId} --format json\` to inspect the per-query breakdown
|
|
24630
|
+
summary: `Run \`canonry discover show ${input.sessionId} --format json\` to inspect the per-query breakdown, then \`canonry discover promote <project> ${input.sessionId}\` to merge cited + aspirational findings into the project.`,
|
|
24486
24631
|
bucketCounts: buckets,
|
|
24487
24632
|
topCompetitors
|
|
24488
24633
|
}),
|
|
@@ -27136,7 +27281,7 @@ async function createServer(opts) {
|
|
|
27136
27281
|
content = `[system] Discovery run ${ctx.runId} failed for project ${project.name}: ${ctx.error ?? "unknown error"}. Surface a one-line diagnosis and a suggested next step.`;
|
|
27137
27282
|
} else {
|
|
27138
27283
|
const top = ctx.topCompetitors.map((c) => `${c.domain}(${c.hits})`).join(", ") || "none";
|
|
27139
|
-
content = `[system] Discovery run ${ctx.runId} completed for project ${project.name} (session ${ctx.sessionId}). Buckets \u2014 cited:${ctx.buckets.cited}, wasted-surface:${ctx.buckets["wasted-surface"]}, aspirational:${ctx.buckets.aspirational} (${ctx.probeCount} probes; seed provider: ${ctx.seedProvider ?? "unknown"}). Top recurring competitor domains: ${top}. Use canonry_discover_session_get to pull per-query buckets and call out
|
|
27284
|
+
content = `[system] Discovery run ${ctx.runId} completed for project ${project.name} (session ${ctx.sessionId}). Buckets \u2014 cited:${ctx.buckets.cited}, wasted-surface:${ctx.buckets["wasted-surface"]}, aspirational:${ctx.buckets.aspirational} (${ctx.probeCount} probes; seed provider: ${ctx.seedProvider ?? "unknown"}). Top recurring competitor domains: ${top}. Use canonry_discover_session_get to pull per-query buckets and call out cited + aspirational findings worth promoting. Keep it tight.`;
|
|
27140
27285
|
}
|
|
27141
27286
|
} else {
|
|
27142
27287
|
content = `[system] Run ${ctx.runId} completed for project ${project.name}. ${ctx.insightCount} insights generated (${ctx.criticalOrHigh} critical/high). Use canonry_run_get to inspect the run and canonry_insights_list to review new findings. Surface anything notable briefly \u2014 skip chit-chat.`;
|
|
@@ -2356,6 +2356,12 @@ var trafficEventsResponseSchema = z20.object({
|
|
|
2356
2356
|
import { z as z21 } from "zod";
|
|
2357
2357
|
var discoveryBucketSchema = z21.enum(["cited", "aspirational", "wasted-surface"]);
|
|
2358
2358
|
var DiscoveryBuckets = discoveryBucketSchema.enum;
|
|
2359
|
+
var DEFAULT_DISCOVERY_PROMOTE_BUCKETS = [
|
|
2360
|
+
DiscoveryBuckets.cited,
|
|
2361
|
+
DiscoveryBuckets.aspirational
|
|
2362
|
+
];
|
|
2363
|
+
var DISCOVERY_PROMOTE_COMPETITOR_CAP = 20;
|
|
2364
|
+
var DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS = 2;
|
|
2359
2365
|
var discoverySessionStatusSchema = z21.enum(["queued", "seeding", "probing", "completed", "failed"]);
|
|
2360
2366
|
var DiscoverySessionStatuses = discoverySessionStatusSchema.enum;
|
|
2361
2367
|
var discoveryCompetitorMapEntrySchema = z21.object({
|
|
@@ -2400,6 +2406,33 @@ var discoveryRunRequestSchema = z21.object({
|
|
|
2400
2406
|
dedupThreshold: z21.number().min(0).max(1).optional(),
|
|
2401
2407
|
maxProbes: z21.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional()
|
|
2402
2408
|
});
|
|
2409
|
+
var discoveryPromoteRequestSchema = z21.object({
|
|
2410
|
+
buckets: z21.array(discoveryBucketSchema).min(1).optional(),
|
|
2411
|
+
includeCompetitors: z21.boolean().optional()
|
|
2412
|
+
});
|
|
2413
|
+
var discoveryPromotePreviewSchema = z21.object({
|
|
2414
|
+
sessionId: z21.string(),
|
|
2415
|
+
projectId: z21.string(),
|
|
2416
|
+
status: discoverySessionStatusSchema,
|
|
2417
|
+
queriesByBucket: z21.object({
|
|
2418
|
+
cited: z21.array(z21.string()),
|
|
2419
|
+
aspirational: z21.array(z21.string()),
|
|
2420
|
+
"wasted-surface": z21.array(z21.string())
|
|
2421
|
+
}),
|
|
2422
|
+
suggestedCompetitors: z21.array(discoveryCompetitorMapEntrySchema)
|
|
2423
|
+
});
|
|
2424
|
+
var discoveryPromoteResultSchema = z21.object({
|
|
2425
|
+
sessionId: z21.string(),
|
|
2426
|
+
projectId: z21.string(),
|
|
2427
|
+
promoted: z21.object({
|
|
2428
|
+
queries: z21.array(z21.string()),
|
|
2429
|
+
competitors: z21.array(z21.string())
|
|
2430
|
+
}),
|
|
2431
|
+
skipped: z21.object({
|
|
2432
|
+
queries: z21.array(z21.string()),
|
|
2433
|
+
competitors: z21.array(z21.string())
|
|
2434
|
+
})
|
|
2435
|
+
});
|
|
2403
2436
|
var queryProvenanceSchema = z21.union([
|
|
2404
2437
|
z21.literal("cli"),
|
|
2405
2438
|
z21.string().regex(/^discovery:.+$/)
|
|
@@ -2637,9 +2670,13 @@ export {
|
|
|
2637
2670
|
TrafficEventKinds,
|
|
2638
2671
|
discoveryBucketSchema,
|
|
2639
2672
|
DiscoveryBuckets,
|
|
2673
|
+
DEFAULT_DISCOVERY_PROMOTE_BUCKETS,
|
|
2674
|
+
DISCOVERY_PROMOTE_COMPETITOR_CAP,
|
|
2675
|
+
DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS,
|
|
2640
2676
|
DiscoverySessionStatuses,
|
|
2641
2677
|
DISCOVERY_MAX_PROBES_CAP,
|
|
2642
2678
|
discoveryRunRequestSchema,
|
|
2679
|
+
discoveryPromoteRequestSchema,
|
|
2643
2680
|
clusterByCosine,
|
|
2644
2681
|
pickClusterRepresentative,
|
|
2645
2682
|
formatRatio,
|
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-HONTKYY7.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-GB3QJURO.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-UEV3HSRL.js";
|
|
53
53
|
import {
|
|
54
54
|
CcReleaseSyncStatuses,
|
|
55
55
|
CheckScopes,
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
SkillsClients,
|
|
63
63
|
TrafficEventKinds,
|
|
64
64
|
determineAnswerMentioned,
|
|
65
|
+
discoveryBucketSchema,
|
|
65
66
|
effectiveDomains,
|
|
66
67
|
formatRunErrorOneLine,
|
|
67
68
|
normalizeUrlPath,
|
|
@@ -69,7 +70,7 @@ import {
|
|
|
69
70
|
providerQuotaPolicySchema,
|
|
70
71
|
resolveProviderInput,
|
|
71
72
|
skillsClientSchema
|
|
72
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-RLLFB3M3.js";
|
|
73
74
|
|
|
74
75
|
// src/cli.ts
|
|
75
76
|
import { pathToFileURL } from "url";
|
|
@@ -621,7 +622,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
621
622
|
return result;
|
|
622
623
|
}
|
|
623
624
|
async function backfillInsightsCommand(project, opts) {
|
|
624
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
625
|
+
const { IntelligenceService } = await import("./intelligence-service-O6KB6YAM.js");
|
|
625
626
|
const config = loadConfig();
|
|
626
627
|
const db = createClient(config.database);
|
|
627
628
|
migrate(db);
|
|
@@ -2054,7 +2055,28 @@ async function discoverPromotePreview(project, sessionId, opts) {
|
|
|
2054
2055
|
for (const c of preview.suggestedCompetitors) console.log(` - ${c.domain} (${c.hits} hits)`);
|
|
2055
2056
|
}
|
|
2056
2057
|
console.log(`
|
|
2057
|
-
|
|
2058
|
+
Run \`canonry discover promote ${project} ${sessionId}\` to merge cited + aspirational queries.`);
|
|
2059
|
+
console.log(" Add `--bucket wasted-surface` only when off-ICP competitor gaps should be tracked.");
|
|
2060
|
+
}
|
|
2061
|
+
async function discoverPromote(project, sessionId, opts) {
|
|
2062
|
+
const client = getClient4();
|
|
2063
|
+
const body = {};
|
|
2064
|
+
if (opts.buckets && opts.buckets.length > 0) body.buckets = opts.buckets;
|
|
2065
|
+
if (opts.includeCompetitors === false) body.includeCompetitors = false;
|
|
2066
|
+
const result = await client.promoteDiscovery(project, sessionId, body);
|
|
2067
|
+
if (opts.format === "json") {
|
|
2068
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
const { promoted, skipped } = result;
|
|
2072
|
+
console.log(`Promoted discovery session ${sessionId} into "${project}":`);
|
|
2073
|
+
console.log(` Queries: ${promoted.queries.length} added, ${skipped.queries.length} already tracked`);
|
|
2074
|
+
for (const q of promoted.queries) console.log(` + ${q}`);
|
|
2075
|
+
console.log(` Competitors: ${promoted.competitors.length} added, ${skipped.competitors.length} already tracked`);
|
|
2076
|
+
for (const c of promoted.competitors) console.log(` + ${c}`);
|
|
2077
|
+
if (promoted.queries.length === 0 && promoted.competitors.length === 0) {
|
|
2078
|
+
console.log(` Nothing new \u2014 the project's basket already covers this session.`);
|
|
2079
|
+
}
|
|
2058
2080
|
}
|
|
2059
2081
|
function printSessionDetail(session) {
|
|
2060
2082
|
console.log(`Discovery session: ${session.id}`);
|
|
@@ -2122,6 +2144,37 @@ Usage: ${usage}`, {
|
|
|
2122
2144
|
}
|
|
2123
2145
|
return parsed;
|
|
2124
2146
|
}
|
|
2147
|
+
function parseBucketsOption(values, usage) {
|
|
2148
|
+
const raw = getStringArray(values, "bucket");
|
|
2149
|
+
if (!raw || raw.length === 0) return void 0;
|
|
2150
|
+
const expanded = raw.flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
2151
|
+
if (expanded.length === 0) {
|
|
2152
|
+
throw usageError(
|
|
2153
|
+
`Error: --bucket must include at least one value (valid: cited, aspirational, wasted-surface)
|
|
2154
|
+
Usage: ${usage}`,
|
|
2155
|
+
{
|
|
2156
|
+
message: "--bucket must include at least one value",
|
|
2157
|
+
details: { command: "discover.promote", usage, option: "bucket", value: raw }
|
|
2158
|
+
}
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
const buckets = [];
|
|
2162
|
+
for (const value of expanded) {
|
|
2163
|
+
const parsed = discoveryBucketSchema.safeParse(value);
|
|
2164
|
+
if (!parsed.success) {
|
|
2165
|
+
throw usageError(
|
|
2166
|
+
`Error: invalid --bucket value "${value}" (valid: cited, aspirational, wasted-surface)
|
|
2167
|
+
Usage: ${usage}`,
|
|
2168
|
+
{
|
|
2169
|
+
message: `invalid --bucket value "${value}"`,
|
|
2170
|
+
details: { command: "discover.promote", usage, option: "bucket", value }
|
|
2171
|
+
}
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
buckets.push(parsed.data);
|
|
2175
|
+
}
|
|
2176
|
+
return buckets;
|
|
2177
|
+
}
|
|
2125
2178
|
var DISCOVER_CLI_COMMANDS = [
|
|
2126
2179
|
{
|
|
2127
2180
|
path: ["discover", "run"],
|
|
@@ -2237,6 +2290,28 @@ var DISCOVER_CLI_COMMANDS = [
|
|
|
2237
2290
|
await discoverShow(project, sessionId, { format: input.format });
|
|
2238
2291
|
}
|
|
2239
2292
|
},
|
|
2293
|
+
{
|
|
2294
|
+
path: ["discover", "promote"],
|
|
2295
|
+
usage: "canonry discover promote <project> <session-id> [--bucket cited,aspirational,wasted-surface] [--no-competitors] [--format json]",
|
|
2296
|
+
options: {
|
|
2297
|
+
bucket: multiStringOption(),
|
|
2298
|
+
"no-competitors": { type: "boolean", default: false }
|
|
2299
|
+
},
|
|
2300
|
+
run: async (input) => {
|
|
2301
|
+
const usage = "canonry discover promote <project> <session-id> [--bucket cited,aspirational,wasted-surface] [--no-competitors] [--format json]";
|
|
2302
|
+
const project = requireProject(input, "discover.promote", usage);
|
|
2303
|
+
const sessionId = requirePositional(input, 1, {
|
|
2304
|
+
command: "discover.promote",
|
|
2305
|
+
usage,
|
|
2306
|
+
message: "session ID is required"
|
|
2307
|
+
});
|
|
2308
|
+
await discoverPromote(project, sessionId, {
|
|
2309
|
+
buckets: parseBucketsOption(input.values, usage),
|
|
2310
|
+
includeCompetitors: !getBoolean(input.values, "no-competitors"),
|
|
2311
|
+
format: input.format
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
},
|
|
2240
2315
|
{
|
|
2241
2316
|
path: ["discover", "promote", "preview"],
|
|
2242
2317
|
usage: "canonry discover promote preview <project> <session-id> [--format json]",
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HONTKYY7.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-GB3QJURO.js";
|
|
7
|
+
import "./chunk-UEV3HSRL.js";
|
|
8
|
+
import "./chunk-RLLFB3M3.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-GB3QJURO.js";
|
|
6
|
+
import "./chunk-RLLFB3M3.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.28.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",
|
|
@@ -60,22 +60,22 @@
|
|
|
60
60
|
"tsup": "^8.5.1",
|
|
61
61
|
"tsx": "^4.19.0",
|
|
62
62
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
63
|
-
"@ainyc/canonry-contracts": "0.0.0",
|
|
64
63
|
"@ainyc/canonry-config": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-intelligence": "0.0.0",
|
|
66
|
-
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
67
|
-
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
68
64
|
"@ainyc/canonry-db": "0.0.0",
|
|
69
65
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-
|
|
66
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
67
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
68
|
+
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
69
|
+
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
71
70
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
71
|
+
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
72
72
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
73
73
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
74
|
-
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
75
74
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
76
|
-
"@ainyc/canonry-provider-
|
|
75
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
76
|
+
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
77
77
|
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
78
|
-
"@ainyc/canonry-provider-
|
|
78
|
+
"@ainyc/canonry-provider-local": "0.0.0"
|
|
79
79
|
},
|
|
80
80
|
"scripts": {
|
|
81
81
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|