@ainyc/canonry 4.71.0 → 4.72.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 (25) hide show
  1. package/assets/agent-workspace/skills/aero/references/regression-playbook.md +1 -1
  2. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +7 -0
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +20 -2
  4. package/assets/assets/{BacklinksPage-CQNPYiDA.js → BacklinksPage-CjfpwZEH.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-BShpLrpS.js → ChartPrimitives-Ckf2FrUy.js} +1 -1
  6. package/assets/assets/{ProjectPage-CJLw1m4O.js → ProjectPage-DZeplYeC.js} +6 -6
  7. package/assets/assets/{RunRow-Dq1vs1hA.js → RunRow-BuFyG0V_.js} +1 -1
  8. package/assets/assets/{RunsPage-CBMa2xWh.js → RunsPage-D-pr000K.js} +1 -1
  9. package/assets/assets/{SettingsPage-B_XeJDdg.js → SettingsPage-CiaapCYn.js} +1 -1
  10. package/assets/assets/{TrafficPage-vJv_Mf6f.js → TrafficPage-B40xytJD.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-C3yFwVmQ.js → TrafficSourceDetailPage-7hHem-gM.js} +1 -1
  12. package/assets/assets/{extract-error-message-CIpeBFLl.js → extract-error-message-3GkDsu1h.js} +1 -1
  13. package/assets/assets/{index-BXLM3-cs.js → index-BVdH2O9w.js} +77 -77
  14. package/assets/assets/{server-traffic-Yt3jIi3g.js → server-traffic-CsgPsudZ.js} +1 -1
  15. package/assets/assets/{trash-2-xGvNHhEj.js → trash-2-B8Ipf9rI.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-ZNWMVYYU.js → chunk-NYZSY5QJ.js} +126 -7
  18. package/dist/{chunk-5FM7QRYD.js → chunk-SJI6JGPN.js} +1249 -1005
  19. package/dist/{chunk-XYBBC5CH.js → chunk-XYX447L2.js} +670 -99
  20. package/dist/{chunk-B32J3DSZ.js → chunk-ZISXWFQA.js} +92 -4
  21. package/dist/cli.js +306 -84
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-GV57RTPO.js → intelligence-service-YOZOOYUI.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +9 -9
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-ZNWMVYYU.js";
12
+ } from "./chunk-NYZSY5QJ.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -95,7 +95,7 @@ import {
95
95
  runs,
96
96
  schedules,
97
97
  usageCounters
98
- } from "./chunk-XYBBC5CH.js";
98
+ } from "./chunk-XYX447L2.js";
99
99
  import {
100
100
  AGENT_MEMORY_VALUE_MAX_BYTES,
101
101
  AGENT_PROVIDER_IDS,
@@ -129,6 +129,7 @@ import {
129
129
  classifySkillFile,
130
130
  coerceSkillManifest,
131
131
  contentActionLabel,
132
+ contentBriefDtoSchema,
132
133
  determineAnswerMentioned,
133
134
  effectiveBrandNames,
134
135
  effectiveDomains,
@@ -142,8 +143,9 @@ import {
142
143
  serializeRunError,
143
144
  skillsClientSchema,
144
145
  validationError,
146
+ winnabilityClassLabel,
145
147
  withRetry
146
- } from "./chunk-5FM7QRYD.js";
148
+ } from "./chunk-SJI6JGPN.js";
147
149
 
148
150
  // src/telemetry.ts
149
151
  import crypto from "crypto";
@@ -5618,7 +5620,7 @@ function readStoredGroundingSources(rawResponse) {
5618
5620
  return result;
5619
5621
  }
5620
5622
  async function backfillInsightsCommand(project, opts) {
5621
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-GV57RTPO.js");
5623
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-YOZOOYUI.js");
5622
5624
  const config = loadConfig();
5623
5625
  const db = createClient(config.database);
5624
5626
  migrate(db);
@@ -8455,6 +8457,89 @@ function createRecommendationExplainer(opts) {
8455
8457
  };
8456
8458
  };
8457
8459
  }
8460
+ var RECOMMENDATION_BRIEF_PROMPT_VERSION = "v1";
8461
+ var BRIEF_SYSTEM_PROMPT = `You are an AEO (Answer Engine Optimization) analyst writing a content brief for a single winnable query. Your audience is the site owner or their agency.
8462
+
8463
+ Return ONLY a single JSON object \u2014 no prose, no markdown, no code fences \u2014 with EXACTLY these keys, each a non-empty string:
8464
+ - "angle": the differentiated content angle to take (what makes this piece win, not generic advice).
8465
+ - "whyWinnable": why this query is winnable for a first-party page, citing the cited-surface signal from the context (competitors vs aggregators, demand, absence).
8466
+ - "schemaHookup": the concrete schema.org type or markup to add or extend (e.g. "FAQPage", "Product + Review", "HowTo").
8467
+ - "controllableSurfaceRationale": why this cited surface is controllable rather than ceded to aggregators/editorial.
8468
+
8469
+ Do not invent facts beyond the supplied context. Be specific and dense.`;
8470
+ function buildBriefPrompt(input) {
8471
+ const base = buildRecommendationPrompt(input);
8472
+ const r = input.recommendation;
8473
+ const winnability = r.winnability === null ? "unknown (no classification coverage)" : r.winnability.toFixed(2);
8474
+ return [
8475
+ base,
8476
+ `Surface class: ${winnabilityClassLabel(r.winnabilityClass).toLowerCase()} (the cited surface is ${r.winnabilityClass === "ceded" ? "dominated by aggregators/editorial" : "controllable"})`,
8477
+ `Winnability: ${winnability}`
8478
+ ].join("\n");
8479
+ }
8480
+ function parseBrief(text, recommendation) {
8481
+ const stripped = text.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
8482
+ let parsed;
8483
+ try {
8484
+ parsed = JSON.parse(stripped);
8485
+ } catch {
8486
+ return null;
8487
+ }
8488
+ if (!parsed || typeof parsed !== "object") return null;
8489
+ const p = parsed;
8490
+ const candidate = {
8491
+ // Deterministic — injected from the recommendation, not the model.
8492
+ targetQuery: recommendation.query,
8493
+ winnabilityClass: recommendation.winnabilityClass,
8494
+ // Creative — taken from the model reply.
8495
+ angle: p.angle,
8496
+ whyWinnable: p.whyWinnable,
8497
+ schemaHookup: p.schemaHookup,
8498
+ controllableSurfaceRationale: p.controllableSurfaceRationale
8499
+ };
8500
+ const result = contentBriefDtoSchema.safeParse(candidate);
8501
+ return result.success ? result.data : null;
8502
+ }
8503
+ function createRecommendationBriefSynthesizer(opts) {
8504
+ return async (input) => {
8505
+ const provider = pickExplainProvider(opts.config, input.providerOverride);
8506
+ const model = resolveModelForCapability(provider, "analyze", input.modelOverride);
8507
+ const apiKey = resolveApiKeyFor(provider, opts.config);
8508
+ const prompt = buildBriefPrompt({
8509
+ projectName: input.projectName,
8510
+ canonicalDomain: input.canonicalDomain,
8511
+ recommendation: input.recommendation
8512
+ });
8513
+ const MAX_ATTEMPTS = 2;
8514
+ let totalCostDollars = 0;
8515
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
8516
+ const userContent = attempt === 0 ? prompt : `${prompt}
8517
+
8518
+ Your previous reply was not valid JSON. Return ONLY a JSON object with exactly the keys: angle, whyWinnable, schemaHookup, controllableSurfaceRationale. No prose, no markdown fences.`;
8519
+ const context = {
8520
+ systemPrompt: BRIEF_SYSTEM_PROMPT,
8521
+ messages: [{ role: "user", content: userContent, timestamp: Date.now() }]
8522
+ };
8523
+ const resp = await complete2(model, context, apiKey ? { apiKey } : {});
8524
+ totalCostDollars += Number.isFinite(resp.usage.cost.total) ? resp.usage.cost.total : 0;
8525
+ const parts = resp.content.filter((p) => p.type === "text");
8526
+ const text = parts.map((p) => p.text).join("\n").trim();
8527
+ const brief = parseBrief(text, input.recommendation);
8528
+ if (brief) {
8529
+ return {
8530
+ promptVersion: RECOMMENDATION_BRIEF_PROMPT_VERSION,
8531
+ provider,
8532
+ model: model.id,
8533
+ brief,
8534
+ costMillicents: dollarsToMillicents(totalCostDollars)
8535
+ };
8536
+ }
8537
+ }
8538
+ throw providerError(
8539
+ `Provider '${provider}' returned unparseable brief output after ${MAX_ATTEMPTS} attempts.`
8540
+ );
8541
+ };
8542
+ }
8458
8543
 
8459
8544
  // src/snapshot-service.ts
8460
8545
  import { runAeoAudit } from "@ainyc/aeo-audit";
@@ -9820,6 +9905,7 @@ async function createServer(opts) {
9820
9905
  return value;
9821
9906
  };
9822
9907
  const explainContentRecommendation = createRecommendationExplainer({ config: opts.config });
9908
+ const briefContentRecommendation = createRecommendationBriefSynthesizer({ config: opts.config });
9823
9909
  await app.register(apiRoutes, {
9824
9910
  db: opts.db,
9825
9911
  routePrefix: apiPrefix,
@@ -9827,6 +9913,8 @@ async function createServer(opts) {
9827
9913
  sessionCookieName: SESSION_COOKIE_NAME,
9828
9914
  resolveSessionApiKeyId,
9829
9915
  explainContentRecommendation,
9916
+ briefContentRecommendation,
9917
+ briefPromptVersion: RECOMMENDATION_BRIEF_PROMPT_VERSION,
9830
9918
  // On-disk paths the daemon depends on. The api-routes plugin uses these
9831
9919
  // to fail loud (HTTP 503) when the operator wipes the DB or config out
9832
9920
  // from under a running serve — SQLite holds the inode open across