@ainyc/canonry 4.100.0 → 4.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +0 -12
  2. package/assets/assets/{BacklinksPage-DWZdWEm3.js → BacklinksPage-aysk_iGy.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-C-ky5206.js → ChartPrimitives-CmKqxndA.js} +1 -1
  4. package/assets/assets/ProjectPage-dduZrrBr.js +7 -0
  5. package/assets/assets/{RunRow-Bj-D4kZe.js → RunRow-Ba77hJ4d.js} +1 -1
  6. package/assets/assets/{RunsPage-GktitsAV.js → RunsPage-D3IAdQMB.js} +1 -1
  7. package/assets/assets/{SettingsPage-AsFLFDS3.js → SettingsPage-Y54JtLw-.js} +1 -1
  8. package/assets/assets/{TrafficPage-Cf3uCkxs.js → TrafficPage-DawzPWdt.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-B5t7nf9C.js → TrafficSourceDetailPage-DAhQwFVb.js} +1 -1
  10. package/assets/assets/{arrow-left-CZFQuQO_.js → arrow-left-NsJbgqmV.js} +1 -1
  11. package/assets/assets/{extract-error-message--bieZ2IX.js → extract-error-message-u00ggxeK.js} +1 -1
  12. package/assets/assets/index-4YArcfwj.js +210 -0
  13. package/assets/assets/index-BCuWbxUj.css +1 -0
  14. package/assets/assets/{trash-2-sKO0k75f.js → trash-2-BBdbbe60.js} +1 -1
  15. package/assets/index.html +2 -2
  16. package/dist/{chunk-UVIHWF45.js → chunk-2YPVK75M.js} +6 -28
  17. package/dist/{chunk-SNHYRSUF.js → chunk-AV3DHH6B.js} +153 -369
  18. package/dist/{chunk-ADLS64PG.js → chunk-HUL5FUBD.js} +6 -105
  19. package/dist/{chunk-74SOIAIY.js → chunk-O3SFVGDF.js} +531 -49
  20. package/dist/cli.js +26 -114
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-3CRNJLUZ.js → intelligence-service-R6QJ3KC2.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +7 -7
  25. package/assets/assets/ProjectPage-CpbFpi6J.js +0 -6
  26. package/assets/assets/index-9hI0ODQU.css +0 -1
  27. package/assets/assets/index-Cb2h7KkD.js +0 -210
@@ -18,7 +18,6 @@ import {
18
18
  DiscoveryBuckets,
19
19
  DiscoveryCompetitorTypes,
20
20
  DiscoverySessionStatuses,
21
- PortfolioChangeTypes,
22
21
  RunKinds,
23
22
  RunStatuses,
24
23
  RunTriggers,
@@ -132,7 +131,6 @@ import {
132
131
  formatIsoDate,
133
132
  formatNumber,
134
133
  formatRatio,
135
- formatRunErrorOneLine,
136
134
  formatWindowCountDelta,
137
135
  ga4AiReferralHistoryEntrySchema,
138
136
  ga4SessionHistoryEntrySchema,
@@ -186,7 +184,6 @@ import {
186
184
  parseRunError,
187
185
  parseWindow,
188
186
  pickClusterRepresentative,
189
- portfolioDtoSchema,
190
187
  projectConfigSchema,
191
188
  projectDtoSchema,
192
189
  projectOverviewDtoSchema,
@@ -266,10 +263,10 @@ import {
266
263
  wordpressSchemaDeployResultDtoSchema,
267
264
  wordpressSchemaStatusResultDtoSchema,
268
265
  wordpressStatusDtoSchema
269
- } from "./chunk-ADLS64PG.js";
266
+ } from "./chunk-HUL5FUBD.js";
270
267
 
271
268
  // src/intelligence-service.ts
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";
269
+ import { eq as eq37, desc as desc18, asc as asc5, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
273
270
 
274
271
  // ../db/src/client.ts
275
272
  import { mkdirSync } from "fs";
@@ -287,6 +284,7 @@ __export(schema_exports, {
287
284
  adsInsightsDaily: () => adsInsightsDaily,
288
285
  agentMemory: () => agentMemory,
289
286
  agentSessions: () => agentSessions,
287
+ agentToolEvents: () => agentToolEvents,
290
288
  aiReferralEventsHourly: () => aiReferralEventsHourly,
291
289
  aiUserFetchEventsHourly: () => aiUserFetchEventsHourly,
292
290
  apiKeys: () => apiKeys,
@@ -324,6 +322,7 @@ __export(schema_exports, {
324
322
  gscUrlInspections: () => gscUrlInspections,
325
323
  healthSnapshots: () => healthSnapshots,
326
324
  insights: () => insights,
325
+ llmUsageEvents: () => llmUsageEvents,
327
326
  migrationsTable: () => migrationsTable,
328
327
  notifications: () => notifications,
329
328
  projects: () => projects,
@@ -927,6 +926,53 @@ var agentMemory = sqliteTable("agent_memory", {
927
926
  uniqueIndex("uniq_agent_memory_project_key").on(table.projectId, table.key),
928
927
  index("idx_agent_memory_project_updated").on(table.projectId, table.updatedAt)
929
928
  ]);
929
+ var llmUsageEvents = sqliteTable("llm_usage_events", {
930
+ id: text("id").primaryKey(),
931
+ projectId: text("project_id").references(() => projects.id, { onDelete: "cascade" }),
932
+ runId: text("run_id").references(() => runs.id, { onDelete: "set null" }),
933
+ agentSessionId: text("agent_session_id").references(() => agentSessions.id, { onDelete: "set null" }),
934
+ feature: text("feature").notNull(),
935
+ provider: text("provider").notNull(),
936
+ model: text("model").notNull(),
937
+ responseId: text("response_id"),
938
+ inputTokens: integer("input_tokens").notNull().default(0),
939
+ outputTokens: integer("output_tokens").notNull().default(0),
940
+ cacheReadTokens: integer("cache_read_tokens").notNull().default(0),
941
+ cacheWriteTokens: integer("cache_write_tokens").notNull().default(0),
942
+ totalTokens: integer("total_tokens").notNull().default(0),
943
+ costMillicents: integer("cost_millicents").notNull().default(0),
944
+ promptFamily: text("prompt_family"),
945
+ promptVersion: text("prompt_version"),
946
+ metadata: text("metadata", { mode: "json" }).$type(),
947
+ createdAt: text("created_at").notNull()
948
+ }, (table) => [
949
+ index("idx_llm_usage_project_created").on(table.projectId, table.createdAt),
950
+ index("idx_llm_usage_feature_created").on(table.feature, table.createdAt),
951
+ index("idx_llm_usage_session_created").on(table.agentSessionId, table.createdAt),
952
+ index("idx_llm_usage_run_created").on(table.runId, table.createdAt)
953
+ ]);
954
+ var agentToolEvents = sqliteTable("agent_tool_events", {
955
+ id: text("id").primaryKey(),
956
+ projectId: text("project_id").references(() => projects.id, { onDelete: "cascade" }),
957
+ agentSessionId: text("agent_session_id").references(() => agentSessions.id, { onDelete: "set null" }),
958
+ toolCallId: text("tool_call_id").notNull(),
959
+ toolName: text("tool_name").notNull(),
960
+ assistantResponseId: text("assistant_response_id"),
961
+ provider: text("provider"),
962
+ model: text("model"),
963
+ status: text("status").notNull(),
964
+ durationMs: integer("duration_ms").notNull().default(0),
965
+ argsBytes: integer("args_bytes").notNull().default(0),
966
+ resultTextChars: integer("result_text_chars").notNull().default(0),
967
+ resultBytes: integer("result_bytes").notNull().default(0),
968
+ metadata: text("metadata", { mode: "json" }).$type(),
969
+ createdAt: text("created_at").notNull()
970
+ }, (table) => [
971
+ index("idx_agent_tool_events_project_created").on(table.projectId, table.createdAt),
972
+ index("idx_agent_tool_events_session_created").on(table.agentSessionId, table.createdAt),
973
+ index("idx_agent_tool_events_tool_created").on(table.toolName, table.createdAt),
974
+ index("idx_agent_tool_events_status_created").on(table.status, table.createdAt)
975
+ ]);
930
976
  var trafficSources = sqliteTable("traffic_sources", {
931
977
  id: text("id").primaryKey(),
932
978
  projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
@@ -3214,6 +3260,63 @@ var MIGRATION_VERSIONS = [
3214
3260
  `ALTER TABLE ads_insights_daily ADD COLUMN conversions INTEGER NOT NULL DEFAULT 0`,
3215
3261
  `ALTER TABLE ads_connections ADD COLUMN conversion_tracking_configured INTEGER NOT NULL DEFAULT 0`
3216
3262
  ]
3263
+ },
3264
+ {
3265
+ version: 84,
3266
+ name: "llm-usage-events",
3267
+ statements: [
3268
+ `CREATE TABLE IF NOT EXISTS llm_usage_events (
3269
+ id TEXT PRIMARY KEY,
3270
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
3271
+ run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
3272
+ agent_session_id TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
3273
+ feature TEXT NOT NULL,
3274
+ provider TEXT NOT NULL,
3275
+ model TEXT NOT NULL,
3276
+ response_id TEXT,
3277
+ input_tokens INTEGER NOT NULL DEFAULT 0,
3278
+ output_tokens INTEGER NOT NULL DEFAULT 0,
3279
+ cache_read_tokens INTEGER NOT NULL DEFAULT 0,
3280
+ cache_write_tokens INTEGER NOT NULL DEFAULT 0,
3281
+ total_tokens INTEGER NOT NULL DEFAULT 0,
3282
+ cost_millicents INTEGER NOT NULL DEFAULT 0,
3283
+ prompt_family TEXT,
3284
+ prompt_version TEXT,
3285
+ metadata TEXT,
3286
+ created_at TEXT NOT NULL
3287
+ )`,
3288
+ `CREATE INDEX IF NOT EXISTS idx_llm_usage_project_created ON llm_usage_events(project_id, created_at)`,
3289
+ `CREATE INDEX IF NOT EXISTS idx_llm_usage_feature_created ON llm_usage_events(feature, created_at)`,
3290
+ `CREATE INDEX IF NOT EXISTS idx_llm_usage_session_created ON llm_usage_events(agent_session_id, created_at)`,
3291
+ `CREATE INDEX IF NOT EXISTS idx_llm_usage_run_created ON llm_usage_events(run_id, created_at)`
3292
+ ]
3293
+ },
3294
+ {
3295
+ version: 85,
3296
+ name: "agent-tool-events",
3297
+ statements: [
3298
+ `CREATE TABLE IF NOT EXISTS agent_tool_events (
3299
+ id TEXT PRIMARY KEY,
3300
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
3301
+ agent_session_id TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
3302
+ tool_call_id TEXT NOT NULL,
3303
+ tool_name TEXT NOT NULL,
3304
+ assistant_response_id TEXT,
3305
+ provider TEXT,
3306
+ model TEXT,
3307
+ status TEXT NOT NULL,
3308
+ duration_ms INTEGER NOT NULL DEFAULT 0,
3309
+ args_bytes INTEGER NOT NULL DEFAULT 0,
3310
+ result_text_chars INTEGER NOT NULL DEFAULT 0,
3311
+ result_bytes INTEGER NOT NULL DEFAULT 0,
3312
+ metadata TEXT,
3313
+ created_at TEXT NOT NULL
3314
+ )`,
3315
+ `CREATE INDEX IF NOT EXISTS idx_agent_tool_events_project_created ON agent_tool_events(project_id, created_at)`,
3316
+ `CREATE INDEX IF NOT EXISTS idx_agent_tool_events_session_created ON agent_tool_events(agent_session_id, created_at)`,
3317
+ `CREATE INDEX IF NOT EXISTS idx_agent_tool_events_tool_created ON agent_tool_events(tool_name, created_at)`,
3318
+ `CREATE INDEX IF NOT EXISTS idx_agent_tool_events_status_created ON agent_tool_events(status, created_at)`
3319
+ ]
3217
3320
  }
3218
3321
  ];
3219
3322
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -5048,233 +5151,6 @@ function buildRunHistory(runs2, snapshotsByRunId, limit = DEFAULT_RUN_HISTORY_LI
5048
5151
  });
5049
5152
  }
5050
5153
 
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
-
5278
5154
  // ../intelligence/src/provider-trends.ts
5279
5155
  function buildProviderTrends(runs2, snapshotsByRunId, limit = 12) {
5280
5156
  const recent = [...runs2].sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, limit).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -7966,6 +7842,10 @@ async function analyticsRoutes(app) {
7966
7842
  }));
7967
7843
  const projectQueries = app.db.select({ id: queries.id, createdAt: queries.createdAt }).from(queries).where(eq10(queries.projectId, project.id)).all();
7968
7844
  const queryCreatedAt = new Map(projectQueries.map((q) => [q.id, q.createdAt]));
7845
+ const mentionShareCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq10(competitors.projectId, project.id)).all().map((c) => ({
7846
+ domain: c.domain,
7847
+ brandTokens: [brandLabelFromDomain(c.domain)].filter((t) => t.length >= 3)
7848
+ }));
7969
7849
  const overall = computeProviderMetric(allSnapshots);
7970
7850
  const byProvider = {};
7971
7851
  const providers = new Set(allSnapshots.map((s) => s.provider));
@@ -7976,7 +7856,7 @@ async function analyticsRoutes(app) {
7976
7856
  const latest = new Date(projectRuns[projectRuns.length - 1].createdAt);
7977
7857
  const spanDays = Math.max(1, Math.ceil((latest.getTime() - earliest.getTime()) / 864e5));
7978
7858
  const bucketSize = bucketSizeForSpan(spanDays);
7979
- const buckets = computeBuckets(allSnapshots, projectRuns, bucketSize, queryCreatedAt);
7859
+ const buckets = computeBuckets(allSnapshots, projectRuns, bucketSize, queryCreatedAt, mentionShareCompetitors);
7980
7860
  const trend = computeTrend(buckets, "citationRate");
7981
7861
  const mentionTrend = computeTrend(buckets, "mentionRate");
7982
7862
  const queryChanges = computeQueryChanges(projectQueries, cutoff);
@@ -8223,7 +8103,7 @@ function computeProviderMetric(snapshots) {
8223
8103
  mentionedCount
8224
8104
  };
8225
8105
  }
8226
- function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
8106
+ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt, mentionShareCompetitors = []) {
8227
8107
  if (projectRuns.length === 0) return [];
8228
8108
  const earliest = new Date(projectRuns[0].createdAt);
8229
8109
  const latest = new Date(projectRuns[projectRuns.length - 1].createdAt);
@@ -8260,6 +8140,7 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
8260
8140
  queryCount,
8261
8141
  mentionRate: metric.mentionRate,
8262
8142
  mentionedCount: metric.mentionedCount,
8143
+ mentionShare: computeMentionShareBucketMetric(usable, mentionShareCompetitors),
8263
8144
  byProvider
8264
8145
  });
8265
8146
  }
@@ -8267,6 +8148,26 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
8267
8148
  }
8268
8149
  return buckets;
8269
8150
  }
8151
+ function computeMentionShareBucketMetric(snapshots, mentionShareCompetitors) {
8152
+ if (mentionShareCompetitors.length === 0) {
8153
+ return { rate: null, projectMentionSnapshots: 0, competitorMentionSnapshots: 0 };
8154
+ }
8155
+ const result = buildMentionShare(
8156
+ snapshots.map((s) => ({
8157
+ projectMentioned: s.resolvedMentioned,
8158
+ answerText: s.answerText
8159
+ })),
8160
+ { competitors: mentionShareCompetitors }
8161
+ );
8162
+ const projectMentionSnapshots = result.breakdown.projectMentionSnapshots;
8163
+ const competitorMentionSnapshots = result.breakdown.competitorMentionSnapshots;
8164
+ const denominator = projectMentionSnapshots + competitorMentionSnapshots;
8165
+ return {
8166
+ rate: denominator > 0 ? round4(projectMentionSnapshots / denominator) : null,
8167
+ projectMentionSnapshots,
8168
+ competitorMentionSnapshots
8169
+ };
8170
+ }
8270
8171
  function computeQueryChanges(projectQueries, cutoff) {
8271
8172
  const byDay = /* @__PURE__ */ new Map();
8272
8173
  for (const q of projectQueries) {
@@ -13337,9 +13238,8 @@ async function visibilityStatsRoutes(app) {
13337
13238
  }
13338
13239
 
13339
13240
  // ../api-routes/src/composites.ts
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";
13241
+ 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";
13341
13242
  var TOP_INSIGHT_LIMIT = 5;
13342
- var PORTFOLIO_RECENT_RUNS_LIMIT = 8;
13343
13243
  var SEARCH_HIT_HARD_LIMIT = 50;
13344
13244
  var SEARCH_SNIPPET_RADIUS = 80;
13345
13245
  var INTEGRATION_SYNC_KINDS = /* @__PURE__ */ new Set([
@@ -13356,14 +13256,6 @@ async function compositeRoutes(app) {
13356
13256
  const project = resolveProject(app.db, request.params.name);
13357
13257
  const filterLocation = (request.query.location ?? "").trim() || null;
13358
13258
  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;
13367
13259
  const allRunsRaw = app.db.select().from(runs).where(and12(eq17(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
13368
13260
  const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
13369
13261
  const totalRuns = allRuns.length;
@@ -13496,111 +13388,8 @@ async function compositeRoutes(app) {
13496
13388
  dateRangeLabel: "All time",
13497
13389
  contextLabel: `${project.country} / ${project.language.toUpperCase()}`
13498
13390
  };
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
- }
13391
+ return reply.send(result);
13392
+ });
13604
13393
  app.get("/projects/:name/search", async (request, reply) => {
13605
13394
  const project = resolveProject(app.db, request.params.name);
13606
13395
  const rawQuery = (request.query.q ?? "").trim();
@@ -14227,7 +14016,6 @@ var SCHEMA_TABLE = {
14227
14016
  LatestProjectRunDto: latestProjectRunDtoSchema,
14228
14017
  LocationContext: locationContextSchema,
14229
14018
  NotificationDto: notificationDtoSchema,
14230
- PortfolioDto: portfolioDtoSchema,
14231
14019
  ProjectDto: projectDtoSchema,
14232
14020
  ProjectOverviewDto: projectOverviewDtoSchema,
14233
14021
  ProjectReportDto: projectReportDtoSchema,
@@ -17482,17 +17270,6 @@ var routeCatalog = [
17482
17270
  404: errorResponse("Project not found.")
17483
17271
  }
17484
17272
  },
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
- },
17496
17273
  {
17497
17274
  method: "get",
17498
17275
  path: "/api/v1/projects/{name}/overview",
@@ -18346,7 +18123,7 @@ var canonryLocalRouteCatalog = [
18346
18123
  method: "post",
18347
18124
  path: "/api/v1/projects/{name}/agent/prompt",
18348
18125
  summary: "Send a prompt to Aero and stream events back as SSE",
18349
- description: 'Posts a prompt into the project\'s Aero session and streams `AgentEvent` frames as `text/event-stream`. Each frame is `data: <JSON>\\n\\n`. The server brackets the stream with `{"type":"stream_open"}` and `{"type":"stream_close"}` control frames; `{"type":"error","message":"..."}` surfaces in-stream failures without collapsing the stream. Returns 409 `AGENT_BUSY` if another turn is already in flight for this project. Body field `scope` accepts "all" | "read-only"; omitted defaults to "read-only" (safe dashboard surface). The CLI passes "all" to keep write tools available.',
18126
+ description: 'Posts a prompt into the project\'s Aero session and streams `AgentEvent` frames as `text/event-stream`. Each frame is `data: <JSON>\\n\\n`. The server brackets the stream with `{"type":"stream_open"}` and `{"type":"stream_close"}` control frames; `{"type":"error","message":"..."}` surfaces in-stream failures without collapsing the stream. Returns 409 `AGENT_BUSY` if another turn is already in flight for this project. Body field `scope` accepts "all" | "read-only"; omitted defaults to "read-only" (safe dashboard surface). Body field `profile` accepts "default" | "ads-operator"; omitted keeps the default full Canonry operator surface. The CLI passes "all" to keep write tools available.',
18350
18127
  tags: ["agent"],
18351
18128
  parameters: [nameParameter],
18352
18129
  requestBody: {
@@ -18371,6 +18148,11 @@ var canonryLocalRouteCatalog = [
18371
18148
  type: "string",
18372
18149
  enum: ["all", "read-only"],
18373
18150
  description: 'Tool surface scope. Default "read-only". Set "all" to enable write tools.'
18151
+ },
18152
+ profile: {
18153
+ type: "string",
18154
+ enum: ["default", "ads-operator"],
18155
+ description: 'Tool profile. Default "default". Set "ads-operator" to use the narrower ads SaaS operator surface plus the ads context tool.'
18374
18156
  }
18375
18157
  }
18376
18158
  }
@@ -21888,7 +21670,7 @@ async function googleRoutes(app, opts) {
21888
21670
 
21889
21671
  // ../api-routes/src/ads.ts
21890
21672
  import crypto19 from "crypto";
21891
- import { eq as eq22, and as and15, asc as asc3, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
21673
+ import { eq as eq22, and as and15, asc as asc2, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
21892
21674
  function statusDto(row) {
21893
21675
  if (!row) return { connected: false };
21894
21676
  return {
@@ -22089,7 +21871,7 @@ async function adsRoutes(app, opts) {
22089
21871
  if (entityId) conditions.push(eq22(adsInsightsDaily.entityId, entityId));
22090
21872
  if (from) conditions.push(gte3(adsInsightsDaily.date, from));
22091
21873
  if (to) conditions.push(lte2(adsInsightsDaily.date, to));
22092
- const rows = app.db.select().from(adsInsightsDaily).where(and15(...conditions)).orderBy(asc3(adsInsightsDaily.date)).all();
21874
+ const rows = app.db.select().from(adsInsightsDaily).where(and15(...conditions)).orderBy(asc2(adsInsightsDaily.date)).all();
22093
21875
  const dtoRows = rows.map((row) => ({
22094
21876
  level: row.level,
22095
21877
  entityId: row.entityId,
@@ -25498,7 +25280,7 @@ async function wordpressRoutes(app, opts) {
25498
25280
 
25499
25281
  // ../api-routes/src/backlinks.ts
25500
25282
  import crypto23 from "crypto";
25501
- import { and as and20, asc as asc4, desc as desc14, eq as eq26, sql as sql10 } from "drizzle-orm";
25283
+ import { and as and20, asc as asc3, desc as desc14, eq as eq26, sql as sql10 } from "drizzle-orm";
25502
25284
 
25503
25285
  // ../integration-commoncrawl/src/constants.ts
25504
25286
  import os2 from "os";
@@ -26257,7 +26039,7 @@ async function backlinksRoutes(app, opts) {
26257
26039
  async (request, reply) => {
26258
26040
  const project = resolveProject(app.db, request.params.name);
26259
26041
  const source = parseSourceParam(request.query.source);
26260
- const rows = app.db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, project.id), eq26(backlinkSummaries.source, source))).orderBy(asc4(backlinkSummaries.queriedAt)).all();
26042
+ const rows = app.db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, project.id), eq26(backlinkSummaries.source, source))).orderBy(asc3(backlinkSummaries.queriedAt)).all();
26261
26043
  const response = rows.map((r) => ({
26262
26044
  release: r.release,
26263
26045
  totalLinkingDomains: r.totalLinkingDomains,
@@ -34222,7 +34004,7 @@ function dedupeStrings(input) {
34222
34004
 
34223
34005
  // ../api-routes/src/technical-aeo.ts
34224
34006
  import crypto29 from "crypto";
34225
- import { and as and26, asc as asc5, count, desc as desc17, eq as eq36, inArray as inArray13 } from "drizzle-orm";
34007
+ import { and as and26, asc as asc4, count, desc as desc17, eq as eq36, inArray as inArray13 } from "drizzle-orm";
34226
34008
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
34227
34009
  function emptyScore(projectName) {
34228
34010
  return {
@@ -34306,7 +34088,7 @@ async function technicalAeoRoutes(app, opts) {
34306
34088
  const total = totalRow?.value ?? 0;
34307
34089
  const limit = parsePositiveInt(request.query.limit, 100, 500);
34308
34090
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
34309
- const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc5(siteAuditPages.url) : asc5(siteAuditPages.overallScore);
34091
+ const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc4(siteAuditPages.url) : asc4(siteAuditPages.overallScore);
34310
34092
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
34311
34093
  const pages = rows.map((row) => ({
34312
34094
  url: row.url,
@@ -35241,7 +35023,7 @@ var IntelligenceService = class {
35241
35023
  // Backfill must not replay probe runs as if they were real sweeps.
35242
35024
  ne5(runs.trigger, RunTriggers.probe)
35243
35025
  )
35244
- ).orderBy(asc6(runs.finishedAt)).all();
35026
+ ).orderBy(asc5(runs.finishedAt)).all();
35245
35027
  let startIdx = 0;
35246
35028
  let endIdx = allRuns.length;
35247
35029
  if (opts?.fromRunId) {
@@ -35549,6 +35331,8 @@ export {
35549
35331
  backlinkDomains,
35550
35332
  backlinkSummaries,
35551
35333
  agentMemory,
35334
+ llmUsageEvents,
35335
+ agentToolEvents,
35552
35336
  crawlerEventsHourly,
35553
35337
  aiUserFetchEventsHourly,
35554
35338
  rawEventSamples,