@ainyc/canonry 2.9.0 → 2.10.3

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/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  setGoogleAuthConfig,
18
18
  showFirstRunNotice,
19
19
  trackEvent
20
- } from "./chunk-MGBXRWLX.js";
20
+ } from "./chunk-FAP76VXF.js";
21
21
  import {
22
22
  CcReleaseSyncStatuses,
23
23
  CliError,
@@ -29,6 +29,7 @@ import {
29
29
  createApiClient,
30
30
  determineAnswerMentioned,
31
31
  effectiveDomains,
32
+ formatRunErrorOneLine,
32
33
  getConfigDir,
33
34
  getConfigPath,
34
35
  isEndpointMissing,
@@ -40,7 +41,7 @@ import {
40
41
  saveConfig,
41
42
  saveConfigPatch,
42
43
  usageError
43
- } from "./chunk-FPZUQADO.js";
44
+ } from "./chunk-Z3BWDCBJ.js";
44
45
  import {
45
46
  apiKeys,
46
47
  competitors,
@@ -50,7 +51,7 @@ import {
50
51
  projects,
51
52
  querySnapshots,
52
53
  runs
53
- } from "./chunk-PYHANJ3B.js";
54
+ } from "./chunk-UM6RDSRJ.js";
54
55
  import "./chunk-MLKGABMK.js";
55
56
 
56
57
  // src/cli.ts
@@ -298,7 +299,7 @@ async function backfillAnswerVisibilityCommand(opts) {
298
299
  console.log(` Errors: ${providerErrors}`);
299
300
  }
300
301
  async function backfillInsightsCommand(project, opts) {
301
- const { IntelligenceService } = await import("./intelligence-service-2ZABHNR4.js");
302
+ const { IntelligenceService } = await import("./intelligence-service-54F3NGPM.js");
302
303
  const config = loadConfig();
303
304
  const db = createClient(config.database);
304
305
  migrate(db);
@@ -677,7 +678,7 @@ async function backlinksExtract(opts) {
677
678
  return;
678
679
  }
679
680
  if (opts.wait) process.stderr.write("\n");
680
- console.log(`Run ${final.id} (${final.status})${final.error ? " \u2014 " + final.error : ""}`);
681
+ console.log(`Run ${final.id} (${final.status})${final.error ? " \u2014 " + formatRunErrorOneLine(final.error) : ""}`);
681
682
  }
682
683
  async function backlinksCachePrune(opts) {
683
684
  if (!opts.release) {
@@ -4923,7 +4924,14 @@ function printRunDetail(run) {
4923
4924
  if (run.startedAt) console.log(` Started: ${run.startedAt}`);
4924
4925
  if (run.finishedAt) console.log(` Finished: ${run.finishedAt}`);
4925
4926
  if (run.createdAt) console.log(` Created: ${run.createdAt}`);
4926
- if (run.error) console.log(` Error: ${run.error}`);
4927
+ if (run.error) {
4928
+ if (run.error.message) console.log(` Error: ${run.error.message}`);
4929
+ if (run.error.providers) {
4930
+ for (const [provider, detail] of Object.entries(run.error.providers)) {
4931
+ console.log(` Error (${provider}): ${detail.message}`);
4932
+ }
4933
+ }
4934
+ }
4927
4935
  if (run.snapshots && run.snapshots.length > 0) {
4928
4936
  console.log(`
4929
4937
  Snapshots: ${run.snapshots.length}`);
@@ -6002,6 +6010,10 @@ async function showHealth(project, opts) {
6002
6010
  console.log(JSON.stringify(health, null, 2));
6003
6011
  return;
6004
6012
  }
6013
+ if (health.status === "no-data") {
6014
+ console.log("No health data yet \u2014 run a sweep first (canonry run <project>).");
6015
+ return;
6016
+ }
6005
6017
  const rate = (health.overallCitedRate * 100).toFixed(1);
6006
6018
  console.log(`Health: ${rate}% cited (${health.citedPairs}/${health.totalPairs} pairs)`);
6007
6019
  console.log("");
@@ -6063,6 +6075,137 @@ var INTELLIGENCE_CLI_COMMANDS = [
6063
6075
  }
6064
6076
  ];
6065
6077
 
6078
+ // src/commands/content.ts
6079
+ async function listContentTargets(project, opts) {
6080
+ const client = createApiClient();
6081
+ const response = await client.getContentTargets(project, {
6082
+ limit: opts.limit,
6083
+ includeInProgress: opts.includeInProgress
6084
+ });
6085
+ if (opts.format === "json") {
6086
+ console.log(JSON.stringify(response, null, 2));
6087
+ return;
6088
+ }
6089
+ if (response.targets.length === 0) {
6090
+ console.log("No content targets surfaced. (Run `canonry run` to generate fresh signal.)");
6091
+ return;
6092
+ }
6093
+ console.log(
6094
+ `${response.targets.length} target${response.targets.length === 1 ? "" : "s"} (latestRunId=${response.contextMetrics.latestRunId})`
6095
+ );
6096
+ console.log("");
6097
+ for (const target of response.targets) {
6098
+ const action = target.action.toUpperCase().padEnd(11);
6099
+ const score = target.score.toFixed(1).padStart(6);
6100
+ const conf = target.actionConfidence.padEnd(6);
6101
+ console.log(`${action} ${score} conf=${conf} ${target.query}`);
6102
+ if (target.ourBestPage) {
6103
+ const posLabel = target.ourBestPage.gscAvgPosition !== null ? `pos #${target.ourBestPage.gscAvgPosition}` : "no GSC ranking";
6104
+ console.log(` our page: ${target.ourBestPage.url} (${posLabel})`);
6105
+ }
6106
+ if (target.winningCompetitor) {
6107
+ console.log(` winning: ${target.winningCompetitor.url} (${target.winningCompetitor.citationCount}\xD7 cited)`);
6108
+ }
6109
+ if (target.drivers.length > 0) {
6110
+ console.log(` why: ${target.drivers.join(" \xB7 ")}`);
6111
+ }
6112
+ if (target.existingAction) {
6113
+ console.log(` in-flight action: ${target.existingAction.actionId} (${target.existingAction.state})`);
6114
+ }
6115
+ console.log("");
6116
+ }
6117
+ }
6118
+ async function listContentSources(project, opts) {
6119
+ const client = createApiClient();
6120
+ const response = await client.getContentSources(project);
6121
+ if (opts.format === "json") {
6122
+ console.log(JSON.stringify(response, null, 2));
6123
+ return;
6124
+ }
6125
+ if (response.sources.length === 0) {
6126
+ console.log("No grounding sources captured yet.");
6127
+ return;
6128
+ }
6129
+ for (const row of response.sources) {
6130
+ console.log(`Q: ${row.query}`);
6131
+ if (row.groundingSources.length === 0) {
6132
+ console.log(" (no grounding sources)");
6133
+ } else {
6134
+ for (const g of row.groundingSources) {
6135
+ const tag = g.isOurDomain ? "OURS " : g.isCompetitor ? "COMP " : "OTHER ";
6136
+ console.log(` ${tag} ${g.uri} (${g.citationCount}\xD7)`);
6137
+ }
6138
+ }
6139
+ console.log("");
6140
+ }
6141
+ }
6142
+ async function listContentGaps(project, opts) {
6143
+ const client = createApiClient();
6144
+ const response = await client.getContentGaps(project);
6145
+ if (opts.format === "json") {
6146
+ console.log(JSON.stringify(response, null, 2));
6147
+ return;
6148
+ }
6149
+ if (response.gaps.length === 0) {
6150
+ console.log("No competitor-only-cited queries detected.");
6151
+ return;
6152
+ }
6153
+ console.log(`${response.gaps.length} gap${response.gaps.length === 1 ? "" : "s"} found`);
6154
+ console.log("");
6155
+ for (const gap of response.gaps) {
6156
+ const missPct = Math.round(gap.missRate * 100);
6157
+ console.log(`${missPct.toString().padStart(3)}% ${gap.competitorCount} competitor(s) ${gap.query}`);
6158
+ console.log(` competitors: ${gap.competitorDomains.join(", ")}`);
6159
+ console.log("");
6160
+ }
6161
+ }
6162
+
6163
+ // src/cli-commands/content.ts
6164
+ var CONTENT_CLI_COMMANDS = [
6165
+ {
6166
+ path: ["content", "targets"],
6167
+ usage: "canonry content targets <project> [--limit <n>] [--include-in-progress] [--format json]",
6168
+ options: {
6169
+ limit: { type: "string" },
6170
+ "include-in-progress": { type: "boolean" }
6171
+ },
6172
+ run: async (input) => {
6173
+ const usage = "canonry content targets <project> [--limit <n>] [--include-in-progress] [--format json]";
6174
+ const project = requireProject(input, "content targets", usage);
6175
+ const limit = parseIntegerOption(input, "limit", {
6176
+ command: "content targets",
6177
+ usage,
6178
+ message: "--limit must be a non-negative integer"
6179
+ });
6180
+ await listContentTargets(project, {
6181
+ limit,
6182
+ includeInProgress: input.values["include-in-progress"] === true,
6183
+ format: input.format
6184
+ });
6185
+ }
6186
+ },
6187
+ {
6188
+ path: ["content", "sources"],
6189
+ usage: "canonry content sources <project> [--format json]",
6190
+ options: {},
6191
+ run: async (input) => {
6192
+ const usage = "canonry content sources <project> [--format json]";
6193
+ const project = requireProject(input, "content sources", usage);
6194
+ await listContentSources(project, { format: input.format });
6195
+ }
6196
+ },
6197
+ {
6198
+ path: ["content", "gaps"],
6199
+ usage: "canonry content gaps <project> [--format json]",
6200
+ options: {},
6201
+ run: async (input) => {
6202
+ const usage = "canonry content gaps <project> [--format json]";
6203
+ const project = requireProject(input, "content gaps", usage);
6204
+ await listContentGaps(project, { format: input.format });
6205
+ }
6206
+ }
6207
+ ];
6208
+
6066
6209
  // src/commands/bootstrap.ts
6067
6210
  import crypto from "crypto";
6068
6211
  import path5 from "path";
@@ -6689,9 +6832,14 @@ Config saved to ${getConfigPath()}`);
6689
6832
  }
6690
6833
 
6691
6834
  // src/commands/serve.ts
6835
+ function resolveServePort(envPort, configPort) {
6836
+ const trimmed = envPort?.trim();
6837
+ if (trimmed) return parseInt(trimmed, 10);
6838
+ return configPort ?? 4100;
6839
+ }
6692
6840
  async function serveCommand(format = "text") {
6693
6841
  const config = loadConfig();
6694
- const port = parseInt(process.env.CANONRY_PORT ?? "4100", 10);
6842
+ const port = resolveServePort(process.env.CANONRY_PORT, config.port);
6695
6843
  const host = process.env.CANONRY_HOST ?? "127.0.0.1";
6696
6844
  config.port = port;
6697
6845
  const db = createClient(config.database);
@@ -6881,11 +7029,9 @@ function applyServerEnv(values) {
6881
7029
  const port = typeof values.port === "string" ? values.port : void 0;
6882
7030
  const host = typeof values.host === "string" ? values.host : void 0;
6883
7031
  const basePath = typeof values["base-path"] === "string" ? values["base-path"] : void 0;
6884
- process.env.CANONRY_PORT = port ?? "4100";
7032
+ if (port) process.env.CANONRY_PORT = port;
6885
7033
  if (host) process.env.CANONRY_HOST = host;
6886
- else delete process.env.CANONRY_HOST;
6887
7034
  if (basePath) process.env.CANONRY_BASE_PATH = basePath;
6888
- else delete process.env.CANONRY_BASE_PATH;
6889
7035
  }
6890
7036
  var SYSTEM_CLI_COMMANDS = [
6891
7037
  {
@@ -8552,6 +8698,7 @@ var REGISTERED_CLI_COMMANDS = [
8552
8698
  ...CDP_CLI_COMMANDS,
8553
8699
  ...GA_CLI_COMMANDS,
8554
8700
  ...INTELLIGENCE_CLI_COMMANDS,
8701
+ ...CONTENT_CLI_COMMANDS,
8555
8702
  ...AGENT_CLI_COMMANDS,
8556
8703
  ...MCP_CLI_COMMANDS
8557
8704
  ];
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-MGBXRWLX.js";
3
+ } from "./chunk-FAP76VXF.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-FPZUQADO.js";
7
- import "./chunk-PYHANJ3B.js";
6
+ } from "./chunk-Z3BWDCBJ.js";
7
+ import "./chunk-UM6RDSRJ.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
  export {
10
10
  createServer,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-PYHANJ3B.js";
3
+ } from "./chunk-UM6RDSRJ.js";
4
4
  import "./chunk-MLKGABMK.js";
5
5
  export {
6
6
  IntelligenceService
package/dist/mcp.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  projectUpsertRequestSchema,
11
11
  runTriggerRequestSchema,
12
12
  scheduleUpsertRequestSchema
13
- } from "./chunk-FPZUQADO.js";
13
+ } from "./chunk-Z3BWDCBJ.js";
14
14
  import "./chunk-MLKGABMK.js";
15
15
 
16
16
  // src/mcp/cli.ts
@@ -319,7 +319,7 @@ var canonryMcpTools = [
319
319
  defineTool({
320
320
  name: "canonry_health_latest",
321
321
  title: "Get latest health",
322
- description: "Get the latest health snapshot for a Canonry project.",
322
+ description: 'Get the latest health snapshot for a Canonry project. Always returns a snapshot once the project exists: real data carries `status: "ready"`; newly-created projects (or projects with only failed runs) carry `status: "no-data"` with `reason: "no-runs-yet"` and zeroed metrics.',
323
323
  access: "read",
324
324
  tier: "monitoring",
325
325
  inputSchema: projectInputSchema,
@@ -753,6 +753,7 @@ var CANONRY_MCP_READ_TOOL_COUNT = canonryMcpTools.filter((tool) => tool.access =
753
753
  var CANONRY_MCP_CORE_TOOL_COUNT = canonryMcpTools.filter((tool) => tool.tier === "core").length;
754
754
 
755
755
  // src/mcp/results.ts
756
+ import { ZodError } from "zod";
756
757
  function jsonToolResult(value) {
757
758
  const result = value === void 0 ? { ok: true } : value;
758
759
  return {
@@ -783,6 +784,15 @@ async function withToolErrors(handler) {
783
784
  }
784
785
  }
785
786
  function toCanonryErrorEnvelope(error) {
787
+ if (error instanceof ZodError) {
788
+ return {
789
+ error: {
790
+ code: "VALIDATION_ERROR",
791
+ message: zodErrorMessage(error),
792
+ details: { issues: error.issues.map(formatZodIssue) }
793
+ }
794
+ };
795
+ }
786
796
  if (error instanceof CliError) {
787
797
  return {
788
798
  error: {
@@ -821,6 +831,15 @@ function hasErrorEnvelope(value) {
821
831
  const error = value.error;
822
832
  return Boolean(error && typeof error === "object");
823
833
  }
834
+ function formatZodIssue(issue) {
835
+ return { path: issue.path.map(String).join("."), message: issue.message };
836
+ }
837
+ function zodErrorMessage(error) {
838
+ const first = error.issues[0];
839
+ if (!first) return "Input validation failed";
840
+ const path = first.path.map(String).join(".");
841
+ return path ? `${path}: ${first.message}` : first.message;
842
+ }
824
843
 
825
844
  // src/mcp/toolkits.ts
826
845
  var CANONRY_MCP_TOOLKIT_NAMES = ["monitoring", "setup", "gsc", "ga", "agent"];
@@ -942,6 +961,7 @@ function createCanonryMcpServerWithCatalog(options = {}) {
942
961
  name: "canonry",
943
962
  version: PACKAGE_VERSION
944
963
  });
964
+ server.validateToolInput = async (_tool, args) => args;
945
965
  const entries = [];
946
966
  for (const registryTool of getCanonryMcpTools(scope)) {
947
967
  const tool = registryTool;
@@ -954,7 +974,10 @@ function createCanonryMcpServerWithCatalog(options = {}) {
954
974
  inputSchema: tool.inputSchema,
955
975
  annotations: tool.annotations
956
976
  },
957
- async (input) => withToolErrors(() => handler(client, input))
977
+ async (input) => withToolErrors(async () => {
978
+ const parsed = tool.inputSchema.parse(input ?? {});
979
+ return handler(client, parsed);
980
+ })
958
981
  );
959
982
  entries.push({ tool, registered });
960
983
  }
@@ -963,6 +986,9 @@ function createCanonryMcpServerWithCatalog(options = {}) {
963
986
  registerMetaTools(server, catalog);
964
987
  return { server, catalog };
965
988
  }
989
+ var loadToolkitInputSchema = z3.object({
990
+ name: z3.enum(CANONRY_MCP_TOOLKIT_NAMES).describe("Toolkit name. List options with canonry_help.")
991
+ });
966
992
  function registerMetaTools(server, catalog) {
967
993
  server.registerTool(
968
994
  "canonry_help",
@@ -979,12 +1005,13 @@ function registerMetaTools(server, catalog) {
979
1005
  {
980
1006
  title: "Load a Canonry MCP toolkit",
981
1007
  description: "Register a toolkit's tools for this session and emit notifications/tools/list_changed. Idempotent. Loaded toolkits remain loaded for the rest of the session.",
982
- inputSchema: {
983
- name: z3.enum(CANONRY_MCP_TOOLKIT_NAMES).describe("Toolkit name. List options with canonry_help.")
984
- },
1008
+ inputSchema: loadToolkitInputSchema.shape,
985
1009
  annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false }
986
1010
  },
987
- async ({ name }) => withToolErrors(async () => catalog.loadToolkit(name))
1011
+ async (input) => withToolErrors(async () => {
1012
+ const parsed = loadToolkitInputSchema.parse(input ?? {});
1013
+ return catalog.loadToolkit(parsed.name);
1014
+ })
988
1015
  );
989
1016
  }
990
1017
  function getCanonryMcpTools(scope = "all") {
@@ -992,12 +1019,46 @@ function getCanonryMcpTools(scope = "all") {
992
1019
  }
993
1020
 
994
1021
  // src/mcp/cli.ts
1022
+ var HELP_TEXT = `Usage: canonry-mcp [--read-only | --scope=<all|read-only>] [--eager]
1023
+
1024
+ Stdio MCP adapter over the Canonry public API. Inherits config from
1025
+ ~/.canonry/config.yaml (or $CANONRY_CONFIG_DIR/config.yaml).
1026
+
1027
+ Flags:
1028
+ --read-only Expose read tools only
1029
+ --scope=<all|read-only>
1030
+ Same as --read-only when "read-only"
1031
+ --eager Load all toolkits at start (skip progressive discovery)
1032
+ --help, -h Show this message
1033
+
1034
+ Environment variables:
1035
+ CANONRY_MCP_SCOPE "all" (default) or "read-only"
1036
+ CANONRY_MCP_EAGER "1" / "true" / "yes" to enable eager mode
1037
+ `;
1038
+ var HelpRequested = class extends Error {
1039
+ constructor() {
1040
+ super("canonry-mcp --help requested");
1041
+ this.name = "HelpRequested";
1042
+ }
1043
+ };
995
1044
  async function main(argv = process.argv.slice(2)) {
996
- const options = parseCliOptions(argv);
1045
+ let options;
1046
+ try {
1047
+ options = parseCliOptions(argv);
1048
+ } catch (error) {
1049
+ if (error instanceof HelpRequested) {
1050
+ process.stderr.write(HELP_TEXT);
1051
+ return;
1052
+ }
1053
+ throw error;
1054
+ }
997
1055
  const server = createCanonryMcpServer({ scope: options.scope, eager: options.eager });
998
1056
  await server.connect(new StdioServerTransport());
999
1057
  }
1000
1058
  function parseCliOptions(argv, env = process.env) {
1059
+ if (argv.includes("--help") || argv.includes("-h")) {
1060
+ throw new HelpRequested();
1061
+ }
1001
1062
  let scope = normalizeScope(env.CANONRY_MCP_SCOPE);
1002
1063
  let eager = parseEagerEnv(env.CANONRY_MCP_EAGER);
1003
1064
  for (let i = 0; i < argv.length; i += 1) {
@@ -1044,6 +1105,8 @@ if (import.meta.url === `file://${process.argv[1]}`) {
1044
1105
  });
1045
1106
  }
1046
1107
  export {
1108
+ HELP_TEXT,
1109
+ HelpRequested,
1047
1110
  main,
1048
1111
  parseCliOptions
1049
1112
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "2.9.0",
3
+ "version": "2.10.3",
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",
@@ -61,18 +61,18 @@
61
61
  "tsx": "^4.19.0",
62
62
  "@ainyc/canonry-api-routes": "0.0.0",
63
63
  "@ainyc/canonry-config": "0.0.0",
64
+ "@ainyc/canonry-db": "0.0.0",
64
65
  "@ainyc/canonry-intelligence": "0.0.0",
65
66
  "@ainyc/canonry-contracts": "0.0.0",
66
- "@ainyc/canonry-db": "0.0.0",
67
67
  "@ainyc/canonry-integration-bing": "0.0.0",
68
68
  "@ainyc/canonry-integration-google": "0.0.0",
69
- "@ainyc/canonry-integration-wordpress": "0.0.0",
70
69
  "@ainyc/canonry-provider-cdp": "0.0.0",
71
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
70
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
72
71
  "@ainyc/canonry-provider-claude": "0.0.0",
73
72
  "@ainyc/canonry-provider-gemini": "0.0.0",
74
- "@ainyc/canonry-provider-local": "0.0.0",
75
73
  "@ainyc/canonry-provider-openai": "0.0.0",
74
+ "@ainyc/canonry-integration-commoncrawl": "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": {