@ainyc/canonry 4.99.1 → 4.100.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/canonry/references/canonry-cli.md +13 -1
- package/assets/assets/{BacklinksPage-v2RC8wlg.js → BacklinksPage-DWZdWEm3.js} +1 -1
- package/assets/assets/{ChartPrimitives-B74e5pqn.js → ChartPrimitives-C-ky5206.js} +1 -1
- package/assets/assets/{ProjectPage-BCzS7TQn.js → ProjectPage-CpbFpi6J.js} +4 -4
- package/assets/assets/{RunRow-BmVPjO-m.js → RunRow-Bj-D4kZe.js} +1 -1
- package/assets/assets/{RunsPage-CK5atXvL.js → RunsPage-GktitsAV.js} +1 -1
- package/assets/assets/{SettingsPage-DarPFUPl.js → SettingsPage-AsFLFDS3.js} +1 -1
- package/assets/assets/{TrafficPage-Ddnn_WqU.js → TrafficPage-Cf3uCkxs.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-g08SKnwZ.js → TrafficSourceDetailPage-B5t7nf9C.js} +1 -1
- package/assets/assets/{arrow-left-D39Vxasb.js → arrow-left-CZFQuQO_.js} +1 -1
- package/assets/assets/{extract-error-message-BiNWqBeh.js → extract-error-message--bieZ2IX.js} +1 -1
- package/assets/assets/index-9hI0ODQU.css +1 -0
- package/assets/assets/index-Cb2h7KkD.js +210 -0
- package/assets/assets/{trash-2-xh6gsnap.js → trash-2-sKO0k75f.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-4Q3LBD7A.js → chunk-74SOIAIY.js} +15 -12
- package/dist/{chunk-JF7EXHGA.js → chunk-ADLS64PG.js} +105 -0
- package/dist/{chunk-YBIT5O62.js → chunk-SNHYRSUF.js} +366 -12
- package/dist/{chunk-IDI6LMYL.js → chunk-UVIHWF45.js} +27 -1
- package/dist/cli.js +110 -4
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-TWCEKVKZ.js → intelligence-service-3CRNJLUZ.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +8 -8
- package/assets/assets/index-BCuWbxUj.css +0 -1
- package/assets/assets/index-Dd4J1VhB.js +0 -210
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
DiscoveryBuckets,
|
|
19
19
|
DiscoveryCompetitorTypes,
|
|
20
20
|
DiscoverySessionStatuses,
|
|
21
|
+
PortfolioChangeTypes,
|
|
21
22
|
RunKinds,
|
|
22
23
|
RunStatuses,
|
|
23
24
|
RunTriggers,
|
|
@@ -131,6 +132,7 @@ import {
|
|
|
131
132
|
formatIsoDate,
|
|
132
133
|
formatNumber,
|
|
133
134
|
formatRatio,
|
|
135
|
+
formatRunErrorOneLine,
|
|
134
136
|
formatWindowCountDelta,
|
|
135
137
|
ga4AiReferralHistoryEntrySchema,
|
|
136
138
|
ga4SessionHistoryEntrySchema,
|
|
@@ -184,6 +186,7 @@ import {
|
|
|
184
186
|
parseRunError,
|
|
185
187
|
parseWindow,
|
|
186
188
|
pickClusterRepresentative,
|
|
189
|
+
portfolioDtoSchema,
|
|
187
190
|
projectConfigSchema,
|
|
188
191
|
projectDtoSchema,
|
|
189
192
|
projectOverviewDtoSchema,
|
|
@@ -263,10 +266,10 @@ import {
|
|
|
263
266
|
wordpressSchemaDeployResultDtoSchema,
|
|
264
267
|
wordpressSchemaStatusResultDtoSchema,
|
|
265
268
|
wordpressStatusDtoSchema
|
|
266
|
-
} from "./chunk-
|
|
269
|
+
} from "./chunk-ADLS64PG.js";
|
|
267
270
|
|
|
268
271
|
// src/intelligence-service.ts
|
|
269
|
-
import { eq as eq37, desc as desc18, asc as
|
|
272
|
+
import { eq as eq37, desc as desc18, asc as asc6, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
|
|
270
273
|
|
|
271
274
|
// ../db/src/client.ts
|
|
272
275
|
import { mkdirSync } from "fs";
|
|
@@ -5045,6 +5048,233 @@ function buildRunHistory(runs2, snapshotsByRunId, limit = DEFAULT_RUN_HISTORY_LI
|
|
|
5045
5048
|
});
|
|
5046
5049
|
}
|
|
5047
5050
|
|
|
5051
|
+
// ../intelligence/src/portfolio-change-feed.ts
|
|
5052
|
+
var PORTFOLIO_CHANGE_FEED_LIMIT = 12;
|
|
5053
|
+
var TONE_SEVERITY = {
|
|
5054
|
+
negative: 0,
|
|
5055
|
+
caution: 1,
|
|
5056
|
+
positive: 2,
|
|
5057
|
+
neutral: 3
|
|
5058
|
+
};
|
|
5059
|
+
function pluralizeQueries(n) {
|
|
5060
|
+
return n === 1 ? "query" : "queries";
|
|
5061
|
+
}
|
|
5062
|
+
function pluralizeAnswers(n) {
|
|
5063
|
+
return n === 1 ? "answer" : "answers";
|
|
5064
|
+
}
|
|
5065
|
+
function affectedDetail(queries2, max = 3) {
|
|
5066
|
+
const list = queries2 ?? [];
|
|
5067
|
+
if (list.length === 0) return "";
|
|
5068
|
+
const shown = list.slice(0, max).map((q) => `"${q}"`).join(", ");
|
|
5069
|
+
const remaining = list.length - max;
|
|
5070
|
+
return remaining > 0 ? `${shown} +${remaining} more` : shown;
|
|
5071
|
+
}
|
|
5072
|
+
function buildProjectChanges(input) {
|
|
5073
|
+
const {
|
|
5074
|
+
projectName,
|
|
5075
|
+
projectSlug,
|
|
5076
|
+
citationMovement,
|
|
5077
|
+
mentionMovement,
|
|
5078
|
+
movementComparison,
|
|
5079
|
+
attentionItems,
|
|
5080
|
+
latestRun
|
|
5081
|
+
} = input;
|
|
5082
|
+
const href = `/projects/${encodeURIComponent(projectSlug)}`;
|
|
5083
|
+
const rows = [];
|
|
5084
|
+
if (!latestRun) {
|
|
5085
|
+
rows.push({
|
|
5086
|
+
id: `${projectSlug}:${PortfolioChangeTypes["project-never-run"]}`,
|
|
5087
|
+
projectName,
|
|
5088
|
+
projectSlug,
|
|
5089
|
+
changeType: PortfolioChangeTypes["project-never-run"],
|
|
5090
|
+
tone: "neutral",
|
|
5091
|
+
title: `${projectName} has no completed sweep yet`,
|
|
5092
|
+
detail: `${input.trackedQueryCount} ${pluralizeQueries(input.trackedQueryCount)} tracked \u2014 run a sweep to start measuring.`,
|
|
5093
|
+
occurredAt: input.projectCreatedAt,
|
|
5094
|
+
href,
|
|
5095
|
+
actionLabel: "Run sweep",
|
|
5096
|
+
comparable: false
|
|
5097
|
+
});
|
|
5098
|
+
return rows;
|
|
5099
|
+
}
|
|
5100
|
+
const { runId, occurredAt } = latestRun;
|
|
5101
|
+
const comparable = movementComparison.comparable;
|
|
5102
|
+
if (latestRun.status === RunStatuses.failed) {
|
|
5103
|
+
rows.push({
|
|
5104
|
+
id: `${projectSlug}:${PortfolioChangeTypes["run-failed"]}:${runId}`,
|
|
5105
|
+
projectName,
|
|
5106
|
+
projectSlug,
|
|
5107
|
+
changeType: PortfolioChangeTypes["run-failed"],
|
|
5108
|
+
tone: "negative",
|
|
5109
|
+
title: `${projectName} sweep failed`,
|
|
5110
|
+
detail: latestRun.error ? formatRunErrorOneLine(latestRun.error) : "The latest visibility sweep failed.",
|
|
5111
|
+
occurredAt,
|
|
5112
|
+
href,
|
|
5113
|
+
actionLabel: "View run",
|
|
5114
|
+
comparable
|
|
5115
|
+
});
|
|
5116
|
+
}
|
|
5117
|
+
if (comparable) {
|
|
5118
|
+
if (citationMovement.lost > 0) {
|
|
5119
|
+
rows.push({
|
|
5120
|
+
id: `${projectSlug}:${PortfolioChangeTypes["citation-lost"]}:${runId}`,
|
|
5121
|
+
projectName,
|
|
5122
|
+
projectSlug,
|
|
5123
|
+
changeType: PortfolioChangeTypes["citation-lost"],
|
|
5124
|
+
tone: "negative",
|
|
5125
|
+
title: `${projectName} lost ${citationMovement.lost} cited ${pluralizeQueries(citationMovement.lost)}`,
|
|
5126
|
+
detail: affectedDetail(citationMovement.lostQueries),
|
|
5127
|
+
occurredAt,
|
|
5128
|
+
href,
|
|
5129
|
+
actionLabel: "Open project",
|
|
5130
|
+
comparable: true
|
|
5131
|
+
});
|
|
5132
|
+
}
|
|
5133
|
+
if (citationMovement.gained > 0) {
|
|
5134
|
+
rows.push({
|
|
5135
|
+
id: `${projectSlug}:${PortfolioChangeTypes["citation-gained"]}:${runId}`,
|
|
5136
|
+
projectName,
|
|
5137
|
+
projectSlug,
|
|
5138
|
+
changeType: PortfolioChangeTypes["citation-gained"],
|
|
5139
|
+
tone: "positive",
|
|
5140
|
+
title: `${projectName} gained ${citationMovement.gained} cited ${pluralizeQueries(citationMovement.gained)}`,
|
|
5141
|
+
detail: affectedDetail(citationMovement.gainedQueries),
|
|
5142
|
+
occurredAt,
|
|
5143
|
+
href,
|
|
5144
|
+
actionLabel: "Open project",
|
|
5145
|
+
comparable: true
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
5148
|
+
if (mentionMovement.lost > 0) {
|
|
5149
|
+
const queries2 = affectedDetail(mentionMovement.lostQueries);
|
|
5150
|
+
rows.push({
|
|
5151
|
+
id: `${projectSlug}:${PortfolioChangeTypes["mention-lost"]}:${runId}`,
|
|
5152
|
+
projectName,
|
|
5153
|
+
projectSlug,
|
|
5154
|
+
changeType: PortfolioChangeTypes["mention-lost"],
|
|
5155
|
+
tone: "negative",
|
|
5156
|
+
title: `${projectName} dropped from ${mentionMovement.lost} ${pluralizeAnswers(mentionMovement.lost)}`,
|
|
5157
|
+
detail: queries2 ? `No longer mentioned for ${queries2}` : "",
|
|
5158
|
+
occurredAt,
|
|
5159
|
+
href,
|
|
5160
|
+
actionLabel: "Open project",
|
|
5161
|
+
comparable: true
|
|
5162
|
+
});
|
|
5163
|
+
}
|
|
5164
|
+
if (mentionMovement.gained > 0) {
|
|
5165
|
+
rows.push({
|
|
5166
|
+
id: `${projectSlug}:${PortfolioChangeTypes["mention-gained"]}:${runId}`,
|
|
5167
|
+
projectName,
|
|
5168
|
+
projectSlug,
|
|
5169
|
+
changeType: PortfolioChangeTypes["mention-gained"],
|
|
5170
|
+
tone: "positive",
|
|
5171
|
+
title: `${projectName} now mentioned in ${mentionMovement.gained} more ${pluralizeAnswers(mentionMovement.gained)}`,
|
|
5172
|
+
detail: affectedDetail(mentionMovement.gainedQueries),
|
|
5173
|
+
occurredAt,
|
|
5174
|
+
href,
|
|
5175
|
+
actionLabel: "Open project",
|
|
5176
|
+
comparable: true
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5179
|
+
} else if (movementComparison.hasPreviousRun && movementComparison.querySetChanged) {
|
|
5180
|
+
rows.push({
|
|
5181
|
+
id: `${projectSlug}:${PortfolioChangeTypes["query-set-changed"]}:${runId}`,
|
|
5182
|
+
projectName,
|
|
5183
|
+
projectSlug,
|
|
5184
|
+
changeType: PortfolioChangeTypes["query-set-changed"],
|
|
5185
|
+
tone: "neutral",
|
|
5186
|
+
title: `${projectName} query set changed`,
|
|
5187
|
+
detail: `+${movementComparison.addedQueryCount} added \xB7 \u2212${movementComparison.removedQueryCount} removed since last sweep \u2014 movement compares the shared queries`,
|
|
5188
|
+
occurredAt,
|
|
5189
|
+
href,
|
|
5190
|
+
actionLabel: "Open project",
|
|
5191
|
+
comparable: false
|
|
5192
|
+
});
|
|
5193
|
+
}
|
|
5194
|
+
for (const item of attentionItems) {
|
|
5195
|
+
if (item.id.startsWith("insight_")) {
|
|
5196
|
+
const isCritical = item.tone === "negative";
|
|
5197
|
+
const changeType = isCritical ? PortfolioChangeTypes["insight-critical"] : PortfolioChangeTypes["insight-high"];
|
|
5198
|
+
rows.push({
|
|
5199
|
+
id: `${projectSlug}:${changeType}:${item.id}`,
|
|
5200
|
+
projectName,
|
|
5201
|
+
projectSlug,
|
|
5202
|
+
changeType,
|
|
5203
|
+
tone: item.tone,
|
|
5204
|
+
title: item.title,
|
|
5205
|
+
detail: item.detail,
|
|
5206
|
+
occurredAt,
|
|
5207
|
+
href,
|
|
5208
|
+
actionLabel: isCritical ? "Critical" : "High",
|
|
5209
|
+
comparable
|
|
5210
|
+
});
|
|
5211
|
+
} else if (item.id === "stale_visibility") {
|
|
5212
|
+
rows.push({
|
|
5213
|
+
id: `${projectSlug}:${PortfolioChangeTypes["stale-visibility"]}:${runId}`,
|
|
5214
|
+
projectName,
|
|
5215
|
+
projectSlug,
|
|
5216
|
+
changeType: PortfolioChangeTypes["stale-visibility"],
|
|
5217
|
+
tone: "caution",
|
|
5218
|
+
title: `${projectName} visibility data is stale`,
|
|
5219
|
+
detail: "Integration syncs have run since the last sweep.",
|
|
5220
|
+
occurredAt,
|
|
5221
|
+
href,
|
|
5222
|
+
actionLabel: "Re-sweep",
|
|
5223
|
+
comparable
|
|
5224
|
+
});
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
return rows;
|
|
5228
|
+
}
|
|
5229
|
+
function resolveEmptyState(projectCount, comparableProjectCount) {
|
|
5230
|
+
if (comparableProjectCount === 0) {
|
|
5231
|
+
return {
|
|
5232
|
+
kind: "awaiting-second-sweep",
|
|
5233
|
+
title: "No changes to compare yet",
|
|
5234
|
+
detail: "Visibility movement appears after a second sweep."
|
|
5235
|
+
};
|
|
5236
|
+
}
|
|
5237
|
+
return {
|
|
5238
|
+
kind: "all-clear",
|
|
5239
|
+
title: "No changes since the last sweep",
|
|
5240
|
+
detail: `All ${projectCount} project${projectCount === 1 ? "" : "s"} held their mention and citation coverage.`
|
|
5241
|
+
};
|
|
5242
|
+
}
|
|
5243
|
+
function buildPortfolioChangeFeed(inputs, _nowIso) {
|
|
5244
|
+
const all = [];
|
|
5245
|
+
let comparableProjectCount = 0;
|
|
5246
|
+
let firstSweepProjectCount = 0;
|
|
5247
|
+
for (const input of inputs) {
|
|
5248
|
+
if (input.latestRun) {
|
|
5249
|
+
if (input.movementComparison.comparable) comparableProjectCount += 1;
|
|
5250
|
+
if (!input.movementComparison.hasPreviousRun) firstSweepProjectCount += 1;
|
|
5251
|
+
}
|
|
5252
|
+
all.push(...buildProjectChanges(input));
|
|
5253
|
+
}
|
|
5254
|
+
const byId = /* @__PURE__ */ new Map();
|
|
5255
|
+
for (const row of all) {
|
|
5256
|
+
if (!byId.has(row.id)) byId.set(row.id, row);
|
|
5257
|
+
}
|
|
5258
|
+
const deduped = [...byId.values()];
|
|
5259
|
+
deduped.sort((a, b) => {
|
|
5260
|
+
if (a.occurredAt !== b.occurredAt) return a.occurredAt < b.occurredAt ? 1 : -1;
|
|
5261
|
+
const severity = TONE_SEVERITY[a.tone] - TONE_SEVERITY[b.tone];
|
|
5262
|
+
if (severity !== 0) return severity;
|
|
5263
|
+
if (a.projectName !== b.projectName) return a.projectName < b.projectName ? -1 : 1;
|
|
5264
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
5265
|
+
});
|
|
5266
|
+
const changeFeedTotal = deduped.length;
|
|
5267
|
+
const changeFeed = deduped.slice(0, PORTFOLIO_CHANGE_FEED_LIMIT);
|
|
5268
|
+
const feedEmptyState = changeFeedTotal === 0 ? resolveEmptyState(inputs.length, comparableProjectCount) : null;
|
|
5269
|
+
return {
|
|
5270
|
+
changeFeed,
|
|
5271
|
+
changeFeedTotal,
|
|
5272
|
+
feedEmptyState,
|
|
5273
|
+
comparableProjectCount,
|
|
5274
|
+
firstSweepProjectCount
|
|
5275
|
+
};
|
|
5276
|
+
}
|
|
5277
|
+
|
|
5048
5278
|
// ../intelligence/src/provider-trends.ts
|
|
5049
5279
|
function buildProviderTrends(runs2, snapshotsByRunId, limit = 12) {
|
|
5050
5280
|
const recent = [...runs2].sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, limit).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -13107,8 +13337,9 @@ async function visibilityStatsRoutes(app) {
|
|
|
13107
13337
|
}
|
|
13108
13338
|
|
|
13109
13339
|
// ../api-routes/src/composites.ts
|
|
13110
|
-
import { eq as eq17, and as and12, desc as desc9, sql as sql7, like as like2, or as or4, inArray as inArray9 } from "drizzle-orm";
|
|
13340
|
+
import { eq as eq17, and as and12, asc as asc2, desc as desc9, sql as sql7, like as like2, or as or4, inArray as inArray9 } from "drizzle-orm";
|
|
13111
13341
|
var TOP_INSIGHT_LIMIT = 5;
|
|
13342
|
+
var PORTFOLIO_RECENT_RUNS_LIMIT = 8;
|
|
13112
13343
|
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
13113
13344
|
var SEARCH_SNIPPET_RADIUS = 80;
|
|
13114
13345
|
var INTEGRATION_SYNC_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -13125,6 +13356,14 @@ async function compositeRoutes(app) {
|
|
|
13125
13356
|
const project = resolveProject(app.db, request.params.name);
|
|
13126
13357
|
const filterLocation = (request.query.location ?? "").trim() || null;
|
|
13127
13358
|
const sinceIso = parseSinceFilter(request.query.since);
|
|
13359
|
+
return reply.send(computeProjectOverview(project, { filterLocation, sinceIso }));
|
|
13360
|
+
});
|
|
13361
|
+
app.get("/portfolio", async (_request, reply) => {
|
|
13362
|
+
return reply.send(buildPortfolioOverview());
|
|
13363
|
+
});
|
|
13364
|
+
function computeProjectOverview(project, opts = {}) {
|
|
13365
|
+
const filterLocation = opts.filterLocation ?? null;
|
|
13366
|
+
const sinceIso = opts.sinceIso ?? null;
|
|
13128
13367
|
const allRunsRaw = app.db.select().from(runs).where(and12(eq17(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
|
|
13129
13368
|
const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
|
|
13130
13369
|
const totalRuns = allRuns.length;
|
|
@@ -13257,8 +13496,111 @@ async function compositeRoutes(app) {
|
|
|
13257
13496
|
dateRangeLabel: "All time",
|
|
13258
13497
|
contextLabel: `${project.country} / ${project.language.toUpperCase()}`
|
|
13259
13498
|
};
|
|
13260
|
-
return
|
|
13261
|
-
}
|
|
13499
|
+
return result;
|
|
13500
|
+
}
|
|
13501
|
+
function buildPortfolioOverview() {
|
|
13502
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13503
|
+
const projectRows = app.db.select().from(projects).orderBy(asc2(projects.name)).all();
|
|
13504
|
+
const trackedCountRows = app.db.select({ projectId: queries.projectId, total: sql7`COUNT(*)` }).from(queries).groupBy(queries.projectId).all();
|
|
13505
|
+
const trackedCountByProject = new Map(trackedCountRows.map((r) => [r.projectId, Number(r.total)]));
|
|
13506
|
+
const lastSweepRow = app.db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and12(
|
|
13507
|
+
eq17(runs.kind, RunKinds["answer-visibility"]),
|
|
13508
|
+
notProbeRun(),
|
|
13509
|
+
inArray9(runs.status, [RunStatuses.completed, RunStatuses.partial])
|
|
13510
|
+
)).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(1).get();
|
|
13511
|
+
const lastSweepAt = lastSweepRow ? lastSweepRow.finishedAt ?? lastSweepRow.createdAt : null;
|
|
13512
|
+
const feedInputs = [];
|
|
13513
|
+
const projectRowDtos = [];
|
|
13514
|
+
const runHistoryById = /* @__PURE__ */ new Map();
|
|
13515
|
+
for (const projectRow of projectRows) {
|
|
13516
|
+
const overview = computeProjectOverview(projectRow);
|
|
13517
|
+
for (const point of overview.runHistory) runHistoryById.set(point.runId, point);
|
|
13518
|
+
const latestVisRunRow = app.db.select().from(runs).where(and12(
|
|
13519
|
+
eq17(runs.projectId, projectRow.id),
|
|
13520
|
+
eq17(runs.kind, RunKinds["answer-visibility"]),
|
|
13521
|
+
notProbeRun()
|
|
13522
|
+
)).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(1).get();
|
|
13523
|
+
const projectName = projectRow.displayName || projectRow.name;
|
|
13524
|
+
feedInputs.push({
|
|
13525
|
+
projectName,
|
|
13526
|
+
projectSlug: projectRow.name,
|
|
13527
|
+
citationMovement: overview.citationMovement,
|
|
13528
|
+
mentionMovement: overview.mentionMovement,
|
|
13529
|
+
movementComparison: overview.movementComparison,
|
|
13530
|
+
attentionItems: overview.attentionItems,
|
|
13531
|
+
latestRun: latestVisRunRow ? {
|
|
13532
|
+
runId: latestVisRunRow.id,
|
|
13533
|
+
status: latestVisRunRow.status,
|
|
13534
|
+
occurredAt: latestVisRunRow.finishedAt ?? latestVisRunRow.createdAt,
|
|
13535
|
+
error: parseRunError(latestVisRunRow.error)
|
|
13536
|
+
} : null,
|
|
13537
|
+
trackedQueryCount: trackedCountByProject.get(projectRow.id) ?? 0,
|
|
13538
|
+
projectCreatedAt: projectRow.createdAt
|
|
13539
|
+
});
|
|
13540
|
+
const mention = overview.scores.mention;
|
|
13541
|
+
projectRowDtos.push({
|
|
13542
|
+
projectSlug: projectRow.name,
|
|
13543
|
+
projectName,
|
|
13544
|
+
canonicalDomain: projectRow.canonicalDomain,
|
|
13545
|
+
mentionScore: mention.progress ?? 0,
|
|
13546
|
+
mentionTone: mention.tone,
|
|
13547
|
+
mentionedOfTotal: { mentioned: overview.queryCounts.mentionedQueries, total: overview.queryCounts.totalQueries },
|
|
13548
|
+
citedOfTotal: { cited: overview.queryCounts.citedQueries, total: overview.queryCounts.totalQueries },
|
|
13549
|
+
mentionDelta: {
|
|
13550
|
+
gained: overview.mentionMovement.gained,
|
|
13551
|
+
lost: overview.mentionMovement.lost,
|
|
13552
|
+
comparable: overview.movementComparison.comparable
|
|
13553
|
+
},
|
|
13554
|
+
citationDelta: {
|
|
13555
|
+
gained: overview.citationMovement.gained,
|
|
13556
|
+
lost: overview.citationMovement.lost,
|
|
13557
|
+
comparable: overview.movementComparison.comparable
|
|
13558
|
+
},
|
|
13559
|
+
competitorPressureLabel: overview.scores.competitorPressure.value,
|
|
13560
|
+
mentionTrend: mention.trend,
|
|
13561
|
+
lastRunAt: latestVisRunRow ? latestVisRunRow.finishedAt ?? latestVisRunRow.createdAt : null,
|
|
13562
|
+
hasEverRun: latestVisRunRow != null
|
|
13563
|
+
});
|
|
13564
|
+
}
|
|
13565
|
+
const feed = buildPortfolioChangeFeed(feedInputs, generatedAt);
|
|
13566
|
+
const projectById = new Map(projectRows.map((p) => [p.id, p]));
|
|
13567
|
+
const recentRunRows = app.db.select().from(runs).where(and12(eq17(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(PORTFOLIO_RECENT_RUNS_LIMIT).all();
|
|
13568
|
+
const recentRuns = [];
|
|
13569
|
+
for (const run of recentRunRows) {
|
|
13570
|
+
const projectRow = projectById.get(run.projectId);
|
|
13571
|
+
if (!projectRow) continue;
|
|
13572
|
+
const point = runHistoryById.get(run.id);
|
|
13573
|
+
const countable = run.status === RunStatuses.completed || run.status === RunStatuses.partial;
|
|
13574
|
+
const error = parseRunError(run.error);
|
|
13575
|
+
recentRuns.push({
|
|
13576
|
+
runId: run.id,
|
|
13577
|
+
projectName: projectRow.displayName || projectRow.name,
|
|
13578
|
+
projectSlug: projectRow.name,
|
|
13579
|
+
kindLabel: "Answer visibility sweep",
|
|
13580
|
+
status: run.status,
|
|
13581
|
+
createdAt: run.createdAt,
|
|
13582
|
+
startedAt: run.startedAt ?? null,
|
|
13583
|
+
finishedAt: run.finishedAt ?? null,
|
|
13584
|
+
durationMs: run.startedAt && run.finishedAt ? new Date(run.finishedAt).getTime() - new Date(run.startedAt).getTime() : null,
|
|
13585
|
+
mentionedCount: countable && point ? point.mentionedCount : null,
|
|
13586
|
+
citedCount: countable && point ? point.citedCount : null,
|
|
13587
|
+
totalCount: countable && point ? point.totalCount : null,
|
|
13588
|
+
errorSummary: error ? formatRunErrorOneLine(error) : null
|
|
13589
|
+
});
|
|
13590
|
+
}
|
|
13591
|
+
return {
|
|
13592
|
+
generatedAt,
|
|
13593
|
+
lastSweepAt,
|
|
13594
|
+
projectCount: projectRows.length,
|
|
13595
|
+
comparableProjectCount: feed.comparableProjectCount,
|
|
13596
|
+
firstSweepProjectCount: feed.firstSweepProjectCount,
|
|
13597
|
+
changeFeed: feed.changeFeed,
|
|
13598
|
+
changeFeedTotal: feed.changeFeedTotal,
|
|
13599
|
+
feedEmptyState: feed.feedEmptyState,
|
|
13600
|
+
recentRuns,
|
|
13601
|
+
projects: projectRowDtos
|
|
13602
|
+
};
|
|
13603
|
+
}
|
|
13262
13604
|
app.get("/projects/:name/search", async (request, reply) => {
|
|
13263
13605
|
const project = resolveProject(app.db, request.params.name);
|
|
13264
13606
|
const rawQuery = (request.query.q ?? "").trim();
|
|
@@ -13885,6 +14227,7 @@ var SCHEMA_TABLE = {
|
|
|
13885
14227
|
LatestProjectRunDto: latestProjectRunDtoSchema,
|
|
13886
14228
|
LocationContext: locationContextSchema,
|
|
13887
14229
|
NotificationDto: notificationDtoSchema,
|
|
14230
|
+
PortfolioDto: portfolioDtoSchema,
|
|
13888
14231
|
ProjectDto: projectDtoSchema,
|
|
13889
14232
|
ProjectOverviewDto: projectOverviewDtoSchema,
|
|
13890
14233
|
ProjectReportDto: projectReportDtoSchema,
|
|
@@ -17139,6 +17482,17 @@ var routeCatalog = [
|
|
|
17139
17482
|
404: errorResponse("Project not found.")
|
|
17140
17483
|
}
|
|
17141
17484
|
},
|
|
17485
|
+
{
|
|
17486
|
+
method: "get",
|
|
17487
|
+
path: "/api/v1/portfolio",
|
|
17488
|
+
summary: "Cross-project portfolio: what changed, recent runs, project state",
|
|
17489
|
+
description: 'One call for the dashboard Portfolio page and `canonry portfolio`. Returns a server-ordered "what changed" feed (citation/mention gains and losses over each project\'s comparable basket, failed sweeps, critical/high insight echoes, stale-visibility, query-set changes, and never-run projects \u2014 recency then severity), a timestamped recent-runs log with independent mention and cited result counts, and a per-project state table. Probe runs are excluded. `generatedAt` is the freshness anchor for every relative timestamp.',
|
|
17490
|
+
tags: ["intelligence"],
|
|
17491
|
+
parameters: [],
|
|
17492
|
+
responses: {
|
|
17493
|
+
200: jsonResponse("Portfolio overview returned.", "PortfolioDto")
|
|
17494
|
+
}
|
|
17495
|
+
},
|
|
17142
17496
|
{
|
|
17143
17497
|
method: "get",
|
|
17144
17498
|
path: "/api/v1/projects/{name}/overview",
|
|
@@ -21534,7 +21888,7 @@ async function googleRoutes(app, opts) {
|
|
|
21534
21888
|
|
|
21535
21889
|
// ../api-routes/src/ads.ts
|
|
21536
21890
|
import crypto19 from "crypto";
|
|
21537
|
-
import { eq as eq22, and as and15, asc as
|
|
21891
|
+
import { eq as eq22, and as and15, asc as asc3, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
|
|
21538
21892
|
function statusDto(row) {
|
|
21539
21893
|
if (!row) return { connected: false };
|
|
21540
21894
|
return {
|
|
@@ -21735,7 +22089,7 @@ async function adsRoutes(app, opts) {
|
|
|
21735
22089
|
if (entityId) conditions.push(eq22(adsInsightsDaily.entityId, entityId));
|
|
21736
22090
|
if (from) conditions.push(gte3(adsInsightsDaily.date, from));
|
|
21737
22091
|
if (to) conditions.push(lte2(adsInsightsDaily.date, to));
|
|
21738
|
-
const rows = app.db.select().from(adsInsightsDaily).where(and15(...conditions)).orderBy(
|
|
22092
|
+
const rows = app.db.select().from(adsInsightsDaily).where(and15(...conditions)).orderBy(asc3(adsInsightsDaily.date)).all();
|
|
21739
22093
|
const dtoRows = rows.map((row) => ({
|
|
21740
22094
|
level: row.level,
|
|
21741
22095
|
entityId: row.entityId,
|
|
@@ -25144,7 +25498,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
25144
25498
|
|
|
25145
25499
|
// ../api-routes/src/backlinks.ts
|
|
25146
25500
|
import crypto23 from "crypto";
|
|
25147
|
-
import { and as and20, asc as
|
|
25501
|
+
import { and as and20, asc as asc4, desc as desc14, eq as eq26, sql as sql10 } from "drizzle-orm";
|
|
25148
25502
|
|
|
25149
25503
|
// ../integration-commoncrawl/src/constants.ts
|
|
25150
25504
|
import os2 from "os";
|
|
@@ -25903,7 +26257,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
25903
26257
|
async (request, reply) => {
|
|
25904
26258
|
const project = resolveProject(app.db, request.params.name);
|
|
25905
26259
|
const source = parseSourceParam(request.query.source);
|
|
25906
|
-
const rows = app.db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, project.id), eq26(backlinkSummaries.source, source))).orderBy(
|
|
26260
|
+
const rows = app.db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, project.id), eq26(backlinkSummaries.source, source))).orderBy(asc4(backlinkSummaries.queriedAt)).all();
|
|
25907
26261
|
const response = rows.map((r) => ({
|
|
25908
26262
|
release: r.release,
|
|
25909
26263
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -33868,7 +34222,7 @@ function dedupeStrings(input) {
|
|
|
33868
34222
|
|
|
33869
34223
|
// ../api-routes/src/technical-aeo.ts
|
|
33870
34224
|
import crypto29 from "crypto";
|
|
33871
|
-
import { and as and26, asc as
|
|
34225
|
+
import { and as and26, asc as asc5, count, desc as desc17, eq as eq36, inArray as inArray13 } from "drizzle-orm";
|
|
33872
34226
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
33873
34227
|
function emptyScore(projectName) {
|
|
33874
34228
|
return {
|
|
@@ -33952,7 +34306,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
33952
34306
|
const total = totalRow?.value ?? 0;
|
|
33953
34307
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
33954
34308
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
33955
|
-
const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ?
|
|
34309
|
+
const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc5(siteAuditPages.url) : asc5(siteAuditPages.overallScore);
|
|
33956
34310
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
33957
34311
|
const pages = rows.map((row) => ({
|
|
33958
34312
|
url: row.url,
|
|
@@ -34887,7 +35241,7 @@ var IntelligenceService = class {
|
|
|
34887
35241
|
// Backfill must not replay probe runs as if they were real sweeps.
|
|
34888
35242
|
ne5(runs.trigger, RunTriggers.probe)
|
|
34889
35243
|
)
|
|
34890
|
-
).orderBy(
|
|
35244
|
+
).orderBy(asc6(runs.finishedAt)).all();
|
|
34891
35245
|
let startIdx = 0;
|
|
34892
35246
|
let endIdx = allRuns.length;
|
|
34893
35247
|
if (opts?.fromRunId) {
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
trafficConnectVercelRequestSchema,
|
|
25
25
|
trafficConnectWordpressRequestSchema,
|
|
26
26
|
trafficEventKindSchema
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-ADLS64PG.js";
|
|
28
28
|
|
|
29
29
|
// src/config.ts
|
|
30
30
|
import fs from "fs";
|
|
@@ -3085,6 +3085,18 @@ var getApiV1ProjectsByNameContentGaps = (options) => {
|
|
|
3085
3085
|
...options
|
|
3086
3086
|
});
|
|
3087
3087
|
};
|
|
3088
|
+
var getApiV1Portfolio = (options) => {
|
|
3089
|
+
return (options?.client ?? client).get({
|
|
3090
|
+
security: [
|
|
3091
|
+
{
|
|
3092
|
+
scheme: "bearer",
|
|
3093
|
+
type: "http"
|
|
3094
|
+
}
|
|
3095
|
+
],
|
|
3096
|
+
url: "/api/v1/portfolio",
|
|
3097
|
+
...options
|
|
3098
|
+
});
|
|
3099
|
+
};
|
|
3088
3100
|
var getApiV1ProjectsByNameOverview = (options) => {
|
|
3089
3101
|
return (options.client ?? client).get({
|
|
3090
3102
|
security: [
|
|
@@ -3908,6 +3920,9 @@ var ApiClient = class {
|
|
|
3908
3920
|
async apply(config) {
|
|
3909
3921
|
return this.invoke(() => postApiV1Apply({ client: this.heyClient, body: config }));
|
|
3910
3922
|
}
|
|
3923
|
+
async getPortfolio() {
|
|
3924
|
+
return this.invoke(() => getApiV1Portfolio({ client: this.heyClient }));
|
|
3925
|
+
}
|
|
3911
3926
|
async getProjectOverview(project, opts) {
|
|
3912
3927
|
return this.invoke(
|
|
3913
3928
|
() => getApiV1ProjectsByNameOverview({
|
|
@@ -5681,6 +5696,17 @@ var canonryMcpTools = [
|
|
|
5681
5696
|
openApiOperations: ["GET /api/v1/projects/{name}/report"],
|
|
5682
5697
|
handler: (client2, input) => client2.getReport(input.project, input.period !== void 0 ? { period: input.period } : void 0)
|
|
5683
5698
|
}),
|
|
5699
|
+
defineTool({
|
|
5700
|
+
name: "canonry_portfolio",
|
|
5701
|
+
title: "Get portfolio change feed (composite)",
|
|
5702
|
+
description: 'One-call cross-project portfolio: a server-ordered "what changed" feed \u2014 citation and mention gains/losses over each project\'s comparable query basket, failed sweeps, critical/high insight echoes, stale-visibility, query-set changes, and never-run projects, ranked by recency then severity \u2014 plus a timestamped recent-runs log carrying BOTH result signals (mentioned vs cited counts, independent) and a per-project state table. Probe runs excluded. Use it to answer "what changed across all my projects" without fanning out per project. `generatedAt` anchors every relative timestamp.',
|
|
5703
|
+
access: "read",
|
|
5704
|
+
tier: "monitoring",
|
|
5705
|
+
inputSchema: emptyInputSchema,
|
|
5706
|
+
annotations: readAnnotations(),
|
|
5707
|
+
openApiOperations: ["GET /api/v1/portfolio"],
|
|
5708
|
+
handler: (client2) => client2.getPortfolio()
|
|
5709
|
+
}),
|
|
5684
5710
|
defineTool({
|
|
5685
5711
|
name: "canonry_analytics_metrics",
|
|
5686
5712
|
title: "Get citation & mention trend",
|