@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.
- package/assets/assets/{index-Ckr4V5dK.js → index-CKzK0os-.js} +93 -93
- package/assets/index.html +1 -1
- package/dist/{chunk-FCYNFM4B.js → chunk-DHMCIJMQ.js} +639 -1
- package/dist/{chunk-PLI7EOPM.js → chunk-XJS7NALL.js} +120 -2
- package/dist/cli.js +94 -4
- package/dist/index.js +2 -2
- package/dist/mcp.js +16 -1
- package/package.json +6 -6
|
@@ -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
|
|
1382
|
-
|
|
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-
|
|
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-
|
|
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
|
|
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(
|
|
8964
|
+
console.log(USAGE2);
|
|
8875
8965
|
return 0;
|
|
8876
8966
|
}
|
|
8877
8967
|
if (args.includes("--version") || args.includes("-v")) {
|
package/dist/index.js
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
projectUpsertRequestSchema,
|
|
11
11
|
runTriggerRequestSchema,
|
|
12
12
|
scheduleUpsertRequestSchema
|
|
13
|
-
} from "./chunk-
|
|
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.
|
|
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-
|
|
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": {
|