@ainyc/canonry 2.12.1 → 2.13.2

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.
@@ -1367,6 +1367,22 @@ var GENERIC_TOKENS = /* @__PURE__ */ new Set([
1367
1367
  "systems",
1368
1368
  "tech"
1369
1369
  ]);
1370
+ var MIN_BRAND_KEY_LENGTH = 6;
1371
+ var BUSINESS_SUFFIXES = [
1372
+ "incorporated",
1373
+ "corporation",
1374
+ "limited",
1375
+ "company",
1376
+ "gmbh",
1377
+ "pllc",
1378
+ "corp",
1379
+ "group",
1380
+ "llp",
1381
+ "plc",
1382
+ "llc",
1383
+ "inc",
1384
+ "ltd"
1385
+ ];
1370
1386
  function extractAnswerMentions(answerText, displayName, domains) {
1371
1387
  if (!answerText) return { mentioned: false, matchedTerms: [] };
1372
1388
  const matchedTerms = [];
@@ -1378,8 +1394,15 @@ function extractAnswerMentions(answerText, displayName, domains) {
1378
1394
  matchedTerms.push(normalizedDomain);
1379
1395
  }
1380
1396
  }
1381
- const normalizedDisplayName = normalizeText(displayName);
1382
- if (normalizedDisplayName && normalizeText(answerText).includes(normalizedDisplayName)) {
1397
+ const answerNormalized = normalizeText(answerText);
1398
+ const answerBrandKey = brandKeyFromText(answerText);
1399
+ const normalizedCandidates = brandNormalizedCandidates(displayName);
1400
+ const brandKeyCandidates = brandKeyCandidatesForMatch(displayName);
1401
+ const matchesNormalized = normalizedCandidates.some((c) => answerNormalized.includes(c));
1402
+ const matchesBrandKey = brandKeyCandidates.some(
1403
+ (c) => c.length >= MIN_BRAND_KEY_LENGTH && answerBrandKey.includes(c)
1404
+ );
1405
+ if (matchesNormalized || matchesBrandKey) {
1383
1406
  matchedTerms.push(displayName);
1384
1407
  }
1385
1408
  const tokens = collectDistinctiveTokens(displayName, domains);
@@ -1445,6 +1468,29 @@ function isDistinctiveToken(token) {
1445
1468
  function normalizeText(value) {
1446
1469
  return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
1447
1470
  }
1471
+ function brandNormalizedCandidates(displayName) {
1472
+ const original = normalizeText(displayName);
1473
+ if (!original) return [];
1474
+ const stripped = stripBusinessSuffix(original, " ");
1475
+ if (!stripped || stripped === original) return [original];
1476
+ if (brandKeyFromText(stripped).length < MIN_BRAND_KEY_LENGTH) return [original];
1477
+ return [original, stripped];
1478
+ }
1479
+ function brandKeyCandidatesForMatch(displayName) {
1480
+ const original = brandKeyFromText(displayName);
1481
+ if (!original) return [];
1482
+ const stripped = stripBusinessSuffix(original, "");
1483
+ return stripped && stripped !== original ? [original, stripped] : [original];
1484
+ }
1485
+ function stripBusinessSuffix(value, separator) {
1486
+ for (const suffix of BUSINESS_SUFFIXES) {
1487
+ const trailing = `${separator}${suffix}`;
1488
+ if (value.endsWith(trailing) && value.length - trailing.length >= 3) {
1489
+ return value.slice(0, -trailing.length);
1490
+ }
1491
+ }
1492
+ return value;
1493
+ }
1448
1494
  function escapeRegExp(value) {
1449
1495
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1450
1496
  }
@@ -1682,6 +1728,68 @@ var contentGapsResponseDtoSchema = z16.object({
1682
1728
  latestRunId: z16.string()
1683
1729
  });
1684
1730
 
1731
+ // ../contracts/src/doctor.ts
1732
+ import { z as z17 } from "zod";
1733
+ var checkStatusSchema = z17.enum(["ok", "warn", "fail", "skipped"]);
1734
+ var CheckStatuses = checkStatusSchema.enum;
1735
+ var checkScopeSchema = z17.enum(["global", "project"]);
1736
+ var CheckScopes = checkScopeSchema.enum;
1737
+ var checkCategorySchema = z17.enum([
1738
+ "auth",
1739
+ "config",
1740
+ "providers",
1741
+ "integrations",
1742
+ "database",
1743
+ "schedules"
1744
+ ]);
1745
+ var CheckCategories = checkCategorySchema.enum;
1746
+ var checkResultSchema = z17.object({
1747
+ id: z17.string(),
1748
+ category: checkCategorySchema,
1749
+ scope: checkScopeSchema,
1750
+ title: z17.string(),
1751
+ status: checkStatusSchema,
1752
+ code: z17.string().describe('Stable machine-readable code (e.g. "google.token.refresh-failed"). Use this for filtering and remediation logic.'),
1753
+ summary: z17.string(),
1754
+ remediation: z17.string().nullable().optional().describe('Operator-facing next step. Null when status is "ok" or no specific remediation applies.'),
1755
+ details: z17.record(z17.string(), z17.unknown()).optional().describe("Structured context \u2014 principal email, redirect URI, missing scopes, etc. Stable per check id."),
1756
+ durationMs: z17.number().int().nonnegative().describe("How long the check took to execute.")
1757
+ });
1758
+ var doctorReportSchema = z17.object({
1759
+ scope: checkScopeSchema,
1760
+ project: z17.string().nullable().describe('Project name when scope is "project", null otherwise.'),
1761
+ generatedAt: z17.string().describe("ISO-8601 timestamp when this doctor run started."),
1762
+ durationMs: z17.number().int().nonnegative(),
1763
+ summary: z17.object({
1764
+ total: z17.number().int().nonnegative(),
1765
+ ok: z17.number().int().nonnegative(),
1766
+ warn: z17.number().int().nonnegative(),
1767
+ fail: z17.number().int().nonnegative(),
1768
+ skipped: z17.number().int().nonnegative()
1769
+ }),
1770
+ checks: z17.array(checkResultSchema)
1771
+ });
1772
+ function summarizeCheckResults(results) {
1773
+ const summary = { total: results.length, ok: 0, warn: 0, fail: 0, skipped: 0 };
1774
+ for (const result of results) {
1775
+ switch (result.status) {
1776
+ case CheckStatuses.ok:
1777
+ summary.ok += 1;
1778
+ break;
1779
+ case CheckStatuses.warn:
1780
+ summary.warn += 1;
1781
+ break;
1782
+ case CheckStatuses.fail:
1783
+ summary.fail += 1;
1784
+ break;
1785
+ case CheckStatuses.skipped:
1786
+ summary.skipped += 1;
1787
+ break;
1788
+ }
1789
+ }
1790
+ return summary;
1791
+ }
1792
+
1685
1793
  // src/client.ts
1686
1794
  function createApiClient() {
1687
1795
  const config = loadConfig();
@@ -2284,6 +2392,11 @@ var ApiClient = class {
2284
2392
  `/projects/${encodeURIComponent(project)}/search?${params.toString()}`
2285
2393
  );
2286
2394
  }
2395
+ async runDoctor(opts = {}) {
2396
+ const qs = opts.checkIds && opts.checkIds.length > 0 ? `?check=${encodeURIComponent(opts.checkIds.join(","))}` : "";
2397
+ const path2 = opts.project ? `/projects/${encodeURIComponent(opts.project)}/doctor${qs}` : `/doctor${qs}`;
2398
+ return this.request("GET", path2);
2399
+ }
2287
2400
  async getHealthHistory(project, limit) {
2288
2401
  const qs = limit ? `?limit=${limit}` : "";
2289
2402
  return this.request("GET", `/projects/${encodeURIComponent(project)}/health/history${qs}`);
@@ -2337,6 +2450,7 @@ export {
2337
2450
  saveConfig,
2338
2451
  saveConfigPatch,
2339
2452
  configExists,
2453
+ EXIT_USER_ERROR,
2340
2454
  EXIT_SYSTEM_ERROR,
2341
2455
  CliError,
2342
2456
  usageError,
@@ -2401,6 +2515,10 @@ export {
2401
2515
  agentMemoryUpsertRequestSchema,
2402
2516
  agentMemoryDeleteRequestSchema,
2403
2517
  CcReleaseSyncStatuses,
2518
+ CheckStatuses,
2519
+ CheckScopes,
2520
+ CheckCategories,
2521
+ summarizeCheckResults,
2404
2522
  createApiClient,
2405
2523
  ApiClient
2406
2524
  };
package/dist/cli.js CHANGED
@@ -17,11 +17,14 @@ import {
17
17
  setGoogleAuthConfig,
18
18
  showFirstRunNotice,
19
19
  trackEvent
20
- } from "./chunk-FCYNFM4B.js";
20
+ } from "./chunk-DHMCIJMQ.js";
21
21
  import {
22
22
  CcReleaseSyncStatuses,
23
+ CheckScopes,
24
+ CheckStatuses,
23
25
  CliError,
24
26
  EXIT_SYSTEM_ERROR,
27
+ EXIT_USER_ERROR,
25
28
  ProviderNames,
26
29
  RunKinds,
27
30
  RunStatuses,
@@ -41,7 +44,7 @@ import {
41
44
  saveConfig,
42
45
  saveConfigPatch,
43
46
  usageError
44
- } from "./chunk-PLI7EOPM.js";
47
+ } from "./chunk-XJS7NALL.js";
45
48
  import {
46
49
  apiKeys,
47
50
  competitors,
@@ -1553,6 +1556,92 @@ var CDP_CLI_COMMANDS = [
1553
1556
  }
1554
1557
  ];
1555
1558
 
1559
+ // src/commands/doctor.ts
1560
+ async function doctorCommand(opts) {
1561
+ const client = createApiClient();
1562
+ const report = await client.runDoctor({
1563
+ project: opts.project,
1564
+ checkIds: opts.checks
1565
+ });
1566
+ if (opts.format === "json") {
1567
+ console.log(JSON.stringify(report, null, 2));
1568
+ } else {
1569
+ printHumanReport(report);
1570
+ }
1571
+ if (report.summary.fail > 0) {
1572
+ throw new CliError({
1573
+ code: "DOCTOR_CHECKS_FAILED",
1574
+ message: `${report.summary.fail} check${report.summary.fail === 1 ? "" : "s"} failed`,
1575
+ exitCode: EXIT_USER_ERROR,
1576
+ details: {
1577
+ scope: report.scope,
1578
+ project: report.project,
1579
+ failed: report.checks.filter((c) => c.status === CheckStatuses.fail).map((c) => c.id)
1580
+ }
1581
+ });
1582
+ }
1583
+ }
1584
+ function statusBadge(status) {
1585
+ switch (status) {
1586
+ case CheckStatuses.ok:
1587
+ return "[ok] ";
1588
+ case CheckStatuses.warn:
1589
+ return "[warn] ";
1590
+ case CheckStatuses.fail:
1591
+ return "[fail] ";
1592
+ case CheckStatuses.skipped:
1593
+ return "[skip] ";
1594
+ }
1595
+ }
1596
+ function printHumanReport(report) {
1597
+ const header = report.scope === CheckScopes.project && report.project ? `canonry doctor \u2014 project "${report.project}"` : "canonry doctor \u2014 global";
1598
+ console.log(`
1599
+ ${header}`);
1600
+ console.log(`(${report.summary.ok} ok, ${report.summary.warn} warn, ${report.summary.fail} fail, ${report.summary.skipped} skipped \u2014 ${report.durationMs}ms)
1601
+ `);
1602
+ if (report.checks.length === 0) {
1603
+ console.log("No checks matched the requested filter.");
1604
+ return;
1605
+ }
1606
+ const grouped = /* @__PURE__ */ new Map();
1607
+ for (const check of report.checks) {
1608
+ const bucket = grouped.get(check.category) ?? [];
1609
+ bucket.push(check);
1610
+ grouped.set(check.category, bucket);
1611
+ }
1612
+ for (const [category, checks] of grouped) {
1613
+ console.log(`${category.toUpperCase()}`);
1614
+ for (const check of checks) {
1615
+ console.log(` ${statusBadge(check.status)}${check.id} \u2014 ${check.summary}`);
1616
+ if (check.remediation) {
1617
+ console.log(` \u2192 ${check.remediation}`);
1618
+ }
1619
+ }
1620
+ console.log();
1621
+ }
1622
+ }
1623
+
1624
+ // src/cli-commands/doctor.ts
1625
+ var USAGE = "canonry doctor [--project <name>] [--check <id>...] [--format json]";
1626
+ var DOCTOR_CLI_COMMANDS = [
1627
+ {
1628
+ path: ["doctor"],
1629
+ usage: USAGE,
1630
+ options: {
1631
+ project: stringOption(),
1632
+ check: multiStringOption()
1633
+ },
1634
+ allowPositionals: false,
1635
+ run: async (input) => {
1636
+ await doctorCommand({
1637
+ project: getString(input.values, "project"),
1638
+ checks: getStringArray(input.values, "check"),
1639
+ format: input.format
1640
+ });
1641
+ }
1642
+ }
1643
+ ];
1644
+
1556
1645
  // src/commands/ga.ts
1557
1646
  function getClient4() {
1558
1647
  return createApiClient();
@@ -8806,12 +8895,13 @@ var REGISTERED_CLI_COMMANDS = [
8806
8895
  ...INTELLIGENCE_CLI_COMMANDS,
8807
8896
  ...CONTENT_CLI_COMMANDS,
8808
8897
  ...AGENT_CLI_COMMANDS,
8898
+ ...DOCTOR_CLI_COMMANDS,
8809
8899
  ...MCP_CLI_COMMANDS
8810
8900
  ];
8811
8901
 
8812
8902
  // src/cli.ts
8813
8903
  import { createRequire as createRequire2 } from "module";
8814
- var USAGE = `
8904
+ var USAGE2 = `
8815
8905
  canonry \u2014 AEO monitoring CLI
8816
8906
 
8817
8907
  Usage: canonry <command> [options]
@@ -8871,7 +8961,7 @@ function extractFormat(cmdArgs) {
8871
8961
  }
8872
8962
  async function runCli(args = process.argv.slice(2)) {
8873
8963
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
8874
- console.log(USAGE);
8964
+ console.log(USAGE2);
8875
8965
  return 0;
8876
8966
  }
8877
8967
  if (args.includes("--version") || args.includes("-v")) {
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-FCYNFM4B.js";
3
+ } from "./chunk-DHMCIJMQ.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-PLI7EOPM.js";
6
+ } from "./chunk-XJS7NALL.js";
7
7
  import "./chunk-UM6RDSRJ.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
  export {
package/dist/mcp.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  projectUpsertRequestSchema,
11
11
  runTriggerRequestSchema,
12
12
  scheduleUpsertRequestSchema
13
- } from "./chunk-PLI7EOPM.js";
13
+ } from "./chunk-XJS7NALL.js";
14
14
  import "./chunk-MLKGABMK.js";
15
15
 
16
16
  // src/mcp/cli.ts
@@ -173,6 +173,10 @@ var agentWebhookAttachInputSchema = z2.object({
173
173
  project: projectNameSchema,
174
174
  url: z2.string().url()
175
175
  });
176
+ var doctorInputSchema = z2.object({
177
+ project: projectNameSchema.optional().describe("Project name to scope project-level checks. Omit to run global checks (provider keys, config, etc.)."),
178
+ checks: z2.array(z2.string().min(1)).optional().describe('Optional check IDs or wildcard prefixes (e.g. "google.auth.*", "config.providers"). Empty/omitted runs all matching checks for the chosen scope.')
179
+ });
176
180
  var AGENT_WEBHOOK_EVENTS = [
177
181
  notificationEventSchema.enum["run.completed"],
178
182
  notificationEventSchema.enum["insight.critical"],
@@ -228,6 +232,17 @@ var canonryMcpTools = [
228
232
  openApiOperations: ["GET /api/v1/projects/{name}/search"],
229
233
  handler: (client, input) => client.searchProject(input.project, { q: input.q, limit: input.limit })
230
234
  }),
235
+ defineTool({
236
+ name: "canonry_doctor",
237
+ title: "Run health checks",
238
+ description: 'Run canonry health checks. With `project`, runs project-scoped checks (Google/GA auth, redirect URI, scopes, property access). Without `project`, runs global checks (provider keys, etc.). Use `checks` to filter by exact ID or wildcard prefix (e.g. ["google.auth.*"]). Returns a structured DoctorReport with per-check status, code, summary, remediation, and details \u2014 use this to diagnose Google auth failures (401/403/redirect-mismatch/principal-mismatch) without parsing logs.',
239
+ access: "read",
240
+ tier: "core",
241
+ inputSchema: doctorInputSchema,
242
+ annotations: readAnnotations(true),
243
+ openApiOperations: ["GET /api/v1/doctor", "GET /api/v1/projects/{name}/doctor"],
244
+ handler: (client, input) => client.runDoctor({ project: input.project, checkIds: input.checks })
245
+ }),
231
246
  defineTool({
232
247
  name: "canonry_project_export",
233
248
  title: "Export project config",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "2.12.1",
3
+ "version": "2.13.2",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -59,20 +59,20 @@
59
59
  "@types/node-cron": "^3.0.11",
60
60
  "tsup": "^8.5.1",
61
61
  "tsx": "^4.19.0",
62
- "@ainyc/canonry-contracts": "0.0.0",
62
+ "@ainyc/canonry-api-routes": "0.0.0",
63
63
  "@ainyc/canonry-db": "0.0.0",
64
- "@ainyc/canonry-intelligence": "0.0.0",
65
64
  "@ainyc/canonry-config": "0.0.0",
66
65
  "@ainyc/canonry-integration-bing": "0.0.0",
66
+ "@ainyc/canonry-intelligence": "0.0.0",
67
67
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
68
- "@ainyc/canonry-api-routes": "0.0.0",
69
68
  "@ainyc/canonry-integration-google": "0.0.0",
70
69
  "@ainyc/canonry-integration-wordpress": "0.0.0",
71
- "@ainyc/canonry-provider-claude": "0.0.0",
72
70
  "@ainyc/canonry-provider-cdp": "0.0.0",
71
+ "@ainyc/canonry-provider-claude": "0.0.0",
72
+ "@ainyc/canonry-contracts": "0.0.0",
73
73
  "@ainyc/canonry-provider-gemini": "0.0.0",
74
- "@ainyc/canonry-provider-local": "0.0.0",
75
74
  "@ainyc/canonry-provider-openai": "0.0.0",
75
+ "@ainyc/canonry-provider-local": "0.0.0",
76
76
  "@ainyc/canonry-provider-perplexity": "0.0.0"
77
77
  },
78
78
  "scripts": {