@ainyc/canonry 2.6.0 → 2.9.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/index.html CHANGED
@@ -12,7 +12,7 @@
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-BsNi8T7D.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-U1lA1GKP.js"></script>
16
16
  <link rel="stylesheet" crossorigin href="./assets/index-CAewPdsZ.css">
17
17
  </head>
18
18
  <body>
@@ -546,6 +546,10 @@ var keywordDtoSchema = z4.object({
546
546
  var keywordBatchRequestSchema = z4.object({
547
547
  keywords: z4.array(z4.string().trim().min(1)).min(1)
548
548
  });
549
+ var keywordGenerateRequestSchema = z4.object({
550
+ provider: providerNameSchema,
551
+ count: z4.number().int().min(1).max(20).optional()
552
+ });
549
553
  var competitorDtoSchema = z4.object({
550
554
  id: z4.string(),
551
555
  domain: z4.string(),
@@ -1677,12 +1681,15 @@ var ApiClient = class {
1677
1681
  async appendKeywords(project, keywords) {
1678
1682
  await this.request("POST", `/projects/${encodeURIComponent(project)}/keywords`, { keywords });
1679
1683
  }
1680
- async putCompetitors(project, competitors) {
1681
- await this.request("PUT", `/projects/${encodeURIComponent(project)}/competitors`, { competitors });
1682
- }
1683
1684
  async listCompetitors(project) {
1684
1685
  return this.request("GET", `/projects/${encodeURIComponent(project)}/competitors`);
1685
1686
  }
1687
+ async appendCompetitors(project, competitors) {
1688
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/competitors`, { competitors });
1689
+ }
1690
+ async deleteCompetitors(project, competitors) {
1691
+ return this.request("DELETE", `/projects/${encodeURIComponent(project)}/competitors`, { competitors });
1692
+ }
1686
1693
  async triggerRun(project, body) {
1687
1694
  return this.request("POST", `/projects/${encodeURIComponent(project)}/runs`, body ?? {});
1688
1695
  }
@@ -2097,6 +2104,7 @@ export {
2097
2104
  hasLocationLabel,
2098
2105
  projectUpsertRequestSchema,
2099
2106
  keywordBatchRequestSchema,
2107
+ keywordGenerateRequestSchema,
2100
2108
  competitorBatchRequestSchema,
2101
2109
  normalizeProjectDomain,
2102
2110
  effectiveDomains,
@@ -18,6 +18,7 @@ import {
18
18
  brandKeyFromText,
19
19
  categorizeSource,
20
20
  categoryLabel,
21
+ competitorBatchRequestSchema,
21
22
  configExists,
22
23
  deliveryFailed,
23
24
  determineAnswerMentioned,
@@ -28,6 +29,7 @@ import {
28
29
  internalError,
29
30
  isAgentProviderId,
30
31
  isBrowserProvider,
32
+ keywordGenerateRequestSchema,
31
33
  loadConfig,
32
34
  locationContextSchema,
33
35
  missingDependency,
@@ -49,7 +51,7 @@ import {
49
51
  visibilityStateFromAnswerMentioned,
50
52
  windowCutoff,
51
53
  wordpressEnvSchema
52
- } from "./chunk-3DUTT6H2.js";
54
+ } from "./chunk-FPZUQADO.js";
53
55
  import {
54
56
  IntelligenceService,
55
57
  agentMemory,
@@ -660,10 +662,16 @@ async function keywordRoutes(app, opts) {
660
662
  });
661
663
  app.post("/projects/:name/keywords/generate", async (request, reply) => {
662
664
  const project = resolveProject(app.db, request.params.name);
663
- const body = request.body;
664
- if (!body?.provider || typeof body.provider !== "string") {
665
- throw validationError('Body must contain a "provider" string');
665
+ const parsed = keywordGenerateRequestSchema.safeParse(request.body);
666
+ if (!parsed.success) {
667
+ throw validationError("Invalid keyword generation request", {
668
+ issues: parsed.error.issues.map((issue) => ({
669
+ path: issue.path.join("."),
670
+ message: issue.message
671
+ }))
672
+ });
666
673
  }
674
+ const body = parsed.data;
667
675
  const provider = body.provider.trim().toLowerCase();
668
676
  const validNames = opts.validProviderNames ?? [];
669
677
  if (validNames.length && !validNames.includes(provider)) {
@@ -672,10 +680,7 @@ async function keywordRoutes(app, opts) {
672
680
  validProviders: validNames
673
681
  });
674
682
  }
675
- if (body.count !== void 0 && (typeof body.count !== "number" || !Number.isFinite(body.count) || !Number.isInteger(body.count))) {
676
- throw validationError('"count" must be an integer');
677
- }
678
- const count = Math.min(Math.max(body.count ?? 5, 1), 20);
683
+ const count = body.count ?? 5;
679
684
  if (!opts.onGenerateKeywords) {
680
685
  throw notImplemented("Key phrase generation is not supported in this deployment");
681
686
  }
@@ -734,6 +739,79 @@ async function competitorRoutes(app) {
734
739
  const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
735
740
  return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
736
741
  });
742
+ app.post("/projects/:name/competitors", async (request, reply) => {
743
+ const project = resolveProject(app.db, request.params.name);
744
+ const body = parseCompetitorBatch(request.body);
745
+ const now = (/* @__PURE__ */ new Date()).toISOString();
746
+ const requested = uniqueStrings(body.competitors);
747
+ app.db.transaction((tx) => {
748
+ const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
749
+ const existingSet = new Set(existing.map((c) => c.domain));
750
+ const added = requested.filter((domain) => !existingSet.has(domain));
751
+ if (added.length === 0) return;
752
+ for (const domain of added) {
753
+ tx.insert(competitors).values({
754
+ id: crypto6.randomUUID(),
755
+ projectId: project.id,
756
+ domain,
757
+ createdAt: now
758
+ }).onConflictDoNothing({
759
+ target: [competitors.projectId, competitors.domain]
760
+ }).run();
761
+ }
762
+ writeAuditLog(tx, {
763
+ projectId: project.id,
764
+ actor: "api",
765
+ action: "competitors.appended",
766
+ entityType: "competitor",
767
+ diff: { added }
768
+ });
769
+ });
770
+ const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
771
+ return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
772
+ });
773
+ app.delete("/projects/:name/competitors", async (request, reply) => {
774
+ const project = resolveProject(app.db, request.params.name);
775
+ const body = parseCompetitorBatch(request.body);
776
+ const requested = new Set(uniqueStrings(body.competitors));
777
+ app.db.transaction((tx) => {
778
+ const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
779
+ const rowsToDelete = existing.filter((c) => requested.has(c.domain));
780
+ if (rowsToDelete.length === 0) return;
781
+ for (const row of rowsToDelete) {
782
+ tx.delete(competitors).where(eq5(competitors.id, row.id)).run();
783
+ }
784
+ writeAuditLog(tx, {
785
+ projectId: project.id,
786
+ actor: "api",
787
+ action: "competitors.deleted",
788
+ entityType: "competitor",
789
+ diff: { deleted: rowsToDelete.map((row) => row.domain) }
790
+ });
791
+ });
792
+ const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
793
+ return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
794
+ });
795
+ }
796
+ function parseCompetitorBatch(value) {
797
+ const result = competitorBatchRequestSchema.safeParse(value);
798
+ if (result.success) return result.data;
799
+ throw validationError("Invalid competitor batch request", {
800
+ issues: result.error.issues.map((issue) => ({
801
+ path: issue.path.join("."),
802
+ message: issue.message
803
+ }))
804
+ });
805
+ }
806
+ function uniqueStrings(values) {
807
+ const seen = /* @__PURE__ */ new Set();
808
+ const result = [];
809
+ for (const value of values) {
810
+ if (seen.has(value)) continue;
811
+ seen.add(value);
812
+ result.push(value);
813
+ }
814
+ return result;
737
815
  }
738
816
 
739
817
  // ../api-routes/src/runs.ts
@@ -985,7 +1063,12 @@ async function runRoutes(app, opts) {
985
1063
  function parseRunTriggerRequest(value) {
986
1064
  const result = runTriggerRequestSchema.safeParse(value);
987
1065
  if (result.success) return result.data;
988
- throw validationError("Invalid run trigger request", { issues: result.error.issues });
1066
+ throw validationError("Invalid run trigger request", {
1067
+ issues: result.error.issues.map((issue) => ({
1068
+ path: issue.path.join("."),
1069
+ message: issue.message
1070
+ }))
1071
+ });
989
1072
  }
990
1073
  function formatRun(row) {
991
1074
  return {
@@ -2658,6 +2741,56 @@ var routeCatalog = [
2658
2741
  200: { description: "Competitors replaced." }
2659
2742
  }
2660
2743
  },
2744
+ {
2745
+ method: "post",
2746
+ path: "/api/v1/projects/{name}/competitors",
2747
+ summary: "Append competitors",
2748
+ tags: ["competitors"],
2749
+ parameters: [nameParameter],
2750
+ requestBody: {
2751
+ required: true,
2752
+ content: {
2753
+ "application/json": {
2754
+ schema: {
2755
+ type: "object",
2756
+ required: ["competitors"],
2757
+ properties: {
2758
+ competitors: stringArraySchema
2759
+ }
2760
+ }
2761
+ }
2762
+ }
2763
+ },
2764
+ responses: {
2765
+ 200: { description: "Competitors appended." },
2766
+ 400: { description: "Invalid competitor append request." }
2767
+ }
2768
+ },
2769
+ {
2770
+ method: "delete",
2771
+ path: "/api/v1/projects/{name}/competitors",
2772
+ summary: "Delete specific competitors",
2773
+ tags: ["competitors"],
2774
+ parameters: [nameParameter],
2775
+ requestBody: {
2776
+ required: true,
2777
+ content: {
2778
+ "application/json": {
2779
+ schema: {
2780
+ type: "object",
2781
+ required: ["competitors"],
2782
+ properties: {
2783
+ competitors: stringArraySchema
2784
+ }
2785
+ }
2786
+ }
2787
+ }
2788
+ },
2789
+ responses: {
2790
+ 200: { description: "Remaining competitors returned." },
2791
+ 400: { description: "Invalid competitor delete request." }
2792
+ }
2793
+ },
2661
2794
  {
2662
2795
  method: "post",
2663
2796
  path: "/api/v1/projects/{name}/runs",
@@ -15395,7 +15528,7 @@ function buildAddCompetitorsTool(ctx) {
15395
15528
  return {
15396
15529
  name: "add_competitors",
15397
15530
  label: "Add competitors",
15398
- description: "Append competitor domains to the project. Fetches the current set, merges with the requested domains (dedup on exact domain match), and persists the combined list.",
15531
+ description: "Append competitor domains to the project. Existing competitors are skipped by the API.",
15399
15532
  parameters: AddCompetitorsSchema,
15400
15533
  execute: async (_toolCallId, params) => {
15401
15534
  const existing = await ctx.client.listCompetitors(ctx.projectName);
@@ -15404,8 +15537,7 @@ function buildAddCompetitorsTool(ctx) {
15404
15537
  if (newDomains.length === 0) {
15405
15538
  return textResult2({ added: [], alreadyTracked: params.domains });
15406
15539
  }
15407
- const merged = [...existing.map((c) => c.domain), ...newDomains];
15408
- await ctx.client.putCompetitors(ctx.projectName, merged);
15540
+ await ctx.client.appendCompetitors(ctx.projectName, newDomains);
15409
15541
  return textResult2({ added: newDomains, alreadyTracked: params.domains.filter((d) => existingDomains.has(d)) });
15410
15542
  }
15411
15543
  };
@@ -16581,7 +16713,7 @@ var SnapshotService = class {
16581
16713
  });
16582
16714
  const enrichedResults = applyBatchAssessment(queryResults, batchAssessment);
16583
16715
  const summary = buildSnapshotSummary(companyName, profile.phrases, providers, enrichedResults, audit, batchAssessment);
16584
- const reportCompetitors = uniqueStrings([
16716
+ const reportCompetitors = uniqueStrings2([
16585
16717
  ...manualCompetitors,
16586
16718
  ...summary.topCompetitors.map((entry) => entry.name)
16587
16719
  ]);
@@ -16634,8 +16766,8 @@ var SnapshotService = class {
16634
16766
  return {
16635
16767
  industry: parsed.industry?.trim() || "Unknown",
16636
16768
  summary: parsed.summary?.trim() || ctx.audit.summary,
16637
- services: uniqueStrings(parsed.services ?? []).slice(0, 6),
16638
- categoryTerms: uniqueStrings(parsed.categoryTerms ?? []).slice(0, 8),
16769
+ services: uniqueStrings2(parsed.services ?? []).slice(0, 6),
16770
+ categoryTerms: uniqueStrings2(parsed.categoryTerms ?? []).slice(0, 8),
16639
16771
  phrases: parsedPhrases
16640
16772
  };
16641
16773
  } catch (err) {
@@ -16703,9 +16835,9 @@ var SnapshotService = class {
16703
16835
  accuracyNotes: null,
16704
16836
  incorrectClaims: [],
16705
16837
  recommendedCompetitors: preliminaryCompetitors,
16706
- citedDomains: uniqueStrings(normalized.citedDomains),
16838
+ citedDomains: uniqueStrings2(normalized.citedDomains),
16707
16839
  groundingSources: normalized.groundingSources,
16708
- searchQueries: uniqueStrings(normalized.searchQueries),
16840
+ searchQueries: uniqueStrings2(normalized.searchQueries),
16709
16841
  answerText: normalized.answerText,
16710
16842
  error: null
16711
16843
  };
@@ -16771,14 +16903,14 @@ var SnapshotService = class {
16771
16903
  mentioned: assessment.mentioned,
16772
16904
  describedAccurately: assessment.describedAccurately,
16773
16905
  accuracyNotes: assessment.accuracyNotes ?? null,
16774
- incorrectClaims: uniqueStrings(assessment.incorrectClaims ?? []).slice(0, 5),
16906
+ incorrectClaims: uniqueStrings2(assessment.incorrectClaims ?? []).slice(0, 5),
16775
16907
  ...hasReviewedCompetitors ? {
16776
- recommendedCompetitors: uniqueStrings(assessment.recommendedCompetitors ?? []).slice(0, 10)
16908
+ recommendedCompetitors: uniqueStrings2(assessment.recommendedCompetitors ?? []).slice(0, 10)
16777
16909
  } : {}
16778
16910
  };
16779
16911
  }),
16780
- whatThisMeans: uniqueStrings(parsed.whatThisMeans ?? []).slice(0, 4),
16781
- recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
16912
+ whatThisMeans: uniqueStrings2(parsed.whatThisMeans ?? []).slice(0, 4),
16913
+ recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
16782
16914
  };
16783
16915
  } catch (err) {
16784
16916
  log12.warn("response.analysis-failed", {
@@ -16873,13 +17005,13 @@ function applyBatchAssessment(queryResults, batchAssessment) {
16873
17005
  };
16874
17006
  }
16875
17007
  const reviewedCompetitors = assessment.recommendedCompetitors;
16876
- const recommendedCompetitors = reviewedCompetitors !== void 0 ? uniqueStrings(reviewedCompetitors) : result.recommendedCompetitors;
17008
+ const recommendedCompetitors = reviewedCompetitors !== void 0 ? uniqueStrings2(reviewedCompetitors) : result.recommendedCompetitors;
16877
17009
  return {
16878
17010
  ...result,
16879
17011
  mentioned: result.mentioned || assessment.mentioned === true,
16880
17012
  describedAccurately: assessment.describedAccurately ?? (result.mentioned ? "unknown" : "not-mentioned"),
16881
17013
  accuracyNotes: assessment.accuracyNotes ?? result.accuracyNotes ?? null,
16882
- incorrectClaims: uniqueStrings([
17014
+ incorrectClaims: uniqueStrings2([
16883
17015
  ...result.incorrectClaims,
16884
17016
  ...assessment.incorrectClaims ?? []
16885
17017
  ]),
@@ -16909,7 +17041,7 @@ function buildSnapshotSummary(companyName, phrases, providers, queryResults, aud
16909
17041
  const whatThisMeans = batchAssessment.whatThisMeans.length > 0 ? batchAssessment.whatThisMeans : [
16910
17042
  defaultMeaning
16911
17043
  ];
16912
- const combinedWhatThisMeans = uniqueStrings([...whatThisMeans, ...failureNote]).slice(0, 5);
17044
+ const combinedWhatThisMeans = uniqueStrings2([...whatThisMeans, ...failureNote]).slice(0, 5);
16913
17045
  const recommendedActions = batchAssessment.recommendedActions.length > 0 ? batchAssessment.recommendedActions : buildFallbackRecommendedActions(audit);
16914
17046
  return {
16915
17047
  totalQueries: phrases.length,
@@ -16952,7 +17084,7 @@ function buildFallbackRecommendedActions(audit) {
16952
17084
  "Add machine-readable trust signals such as schema, FAQs, and llms.txt support.",
16953
17085
  "Build comparison and proof content that makes the category fit unmistakable."
16954
17086
  ];
16955
- return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
17087
+ return uniqueStrings2([...weakestFactors, ...defaults]).slice(0, 4);
16956
17088
  }
16957
17089
  function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
16958
17090
  const normalizedTarget = extractHostname2(targetDomain);
@@ -17026,11 +17158,11 @@ function parseJsonObject(input) {
17026
17158
  }
17027
17159
  function normalizeStringList(values) {
17028
17160
  const items = values.flatMap((value) => value.split(","));
17029
- return uniqueStrings(
17161
+ return uniqueStrings2(
17030
17162
  items.map((value) => value.trim()).filter(Boolean)
17031
17163
  );
17032
17164
  }
17033
- function uniqueStrings(values) {
17165
+ function uniqueStrings2(values) {
17034
17166
  if (!Array.isArray(values)) return [];
17035
17167
  return [...new Set(
17036
17168
  values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)