@ainyc/canonry 2.9.0 → 2.10.1

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-U1lA1GKP.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-PhLDQh1e.js"></script>
16
16
  <link rel="stylesheet" crossorigin href="./assets/index-CAewPdsZ.css">
17
17
  </head>
18
18
  <body>
@@ -376,6 +376,18 @@ var runTriggerRequestSchema = z2.object({
376
376
  (data) => Number(Boolean(data.location)) + Number(Boolean(data.allLocations)) + Number(Boolean(data.noLocation)) <= 1,
377
377
  { message: 'Only one of "location", "allLocations", or "noLocation" may be provided' }
378
378
  );
379
+ var runProviderErrorSchema = z2.object({
380
+ /** Human-readable error message (best-effort extracted from `raw.error.message` / `raw.message`, otherwise the raw text with any `[provider-X]` prefix stripped). */
381
+ message: z2.string(),
382
+ /** Original provider response payload, if the underlying error body parsed as JSON. Use this for structured fields like HTTP status, error code, etc. */
383
+ raw: z2.unknown().optional()
384
+ });
385
+ var runErrorSchema = z2.object({
386
+ /** Top-level message for runs that failed without a per-provider error (e.g. user cancellation, internal scheduling failures). */
387
+ message: z2.string().optional(),
388
+ /** Per-provider errors for visibility-sweep runs that had at least one provider fail. */
389
+ providers: z2.record(z2.string(), runProviderErrorSchema).optional()
390
+ });
379
391
  var runDtoSchema = z2.object({
380
392
  id: z2.string(),
381
393
  projectId: z2.string(),
@@ -385,9 +397,70 @@ var runDtoSchema = z2.object({
385
397
  location: z2.string().nullable().optional(),
386
398
  startedAt: z2.string().nullable().optional(),
387
399
  finishedAt: z2.string().nullable().optional(),
388
- error: z2.string().nullable().optional(),
400
+ error: runErrorSchema.nullable().optional(),
389
401
  createdAt: z2.string()
390
402
  });
403
+ var PROVIDER_PREFIX = /^\[provider-[a-zA-Z0-9_-]+\]\s+/;
404
+ function parseProviderErrorMessage(msg) {
405
+ const stripped = msg.replace(PROVIDER_PREFIX, "");
406
+ try {
407
+ const raw = JSON.parse(stripped);
408
+ if (raw && typeof raw === "object") {
409
+ const inner = raw;
410
+ const fromErrorMessage = typeof inner.error?.message === "string" ? inner.error.message : void 0;
411
+ const fromMessage = typeof inner.message === "string" ? inner.message : void 0;
412
+ return { message: fromErrorMessage ?? fromMessage ?? stripped, raw };
413
+ }
414
+ } catch {
415
+ }
416
+ return { message: stripped };
417
+ }
418
+ function parseRunError(raw) {
419
+ if (!raw) return null;
420
+ let parsed;
421
+ try {
422
+ parsed = JSON.parse(raw);
423
+ } catch {
424
+ return { message: raw };
425
+ }
426
+ if (!parsed || typeof parsed !== "object") {
427
+ return { message: raw };
428
+ }
429
+ const obj = parsed;
430
+ const hasProviders = obj.providers && typeof obj.providers === "object";
431
+ const hasMessage = typeof obj.message === "string";
432
+ if (hasProviders || hasMessage) {
433
+ return parsed;
434
+ }
435
+ const providers = {};
436
+ for (const [name, val] of Object.entries(obj)) {
437
+ providers[name] = parseProviderErrorMessage(typeof val === "string" ? val : JSON.stringify(val));
438
+ }
439
+ return { providers };
440
+ }
441
+ function buildRunErrorFromMessages(messages) {
442
+ const providers = {};
443
+ for (const [name, msg] of messages) {
444
+ providers[name] = parseProviderErrorMessage(msg);
445
+ }
446
+ return { providers };
447
+ }
448
+ function serializeRunError(err) {
449
+ return JSON.stringify(err);
450
+ }
451
+ function formatRunErrorOneLine(err) {
452
+ if (err.providers) {
453
+ const entries = Object.entries(err.providers);
454
+ if (entries.length === 1) {
455
+ const [provider, detail] = entries[0];
456
+ return `${provider}: ${detail.message}`;
457
+ }
458
+ if (entries.length > 1) {
459
+ return entries.map(([p, d]) => `${p}: ${d.message}`).join(" \u2022 ");
460
+ }
461
+ }
462
+ return err.message ?? "Run failed.";
463
+ }
391
464
  var groundingSourceSchema = z2.object({
392
465
  uri: z2.string(),
393
466
  title: z2.string()
@@ -2131,6 +2204,10 @@ export {
2131
2204
  RunKinds,
2132
2205
  RunTriggers,
2133
2206
  runTriggerRequestSchema,
2207
+ parseRunError,
2208
+ buildRunErrorFromMessages,
2209
+ serializeRunError,
2210
+ formatRunErrorOneLine,
2134
2211
  snapshotRequestSchema,
2135
2212
  scheduleUpsertRequestSchema,
2136
2213
  parseWindow,
@@ -16,6 +16,7 @@ import {
16
16
  authInvalid,
17
17
  authRequired,
18
18
  brandKeyFromText,
19
+ buildRunErrorFromMessages,
19
20
  categorizeSource,
20
21
  categoryLabel,
21
22
  competitorBatchRequestSchema,
@@ -36,6 +37,7 @@ import {
36
37
  normalizeProjectDomain,
37
38
  notFound,
38
39
  notImplemented,
40
+ parseRunError,
39
41
  parseWindow,
40
42
  projectConfigSchema,
41
43
  projectUpsertRequestSchema,
@@ -45,13 +47,14 @@ import {
45
47
  runTriggerRequestSchema,
46
48
  saveConfigPatch,
47
49
  scheduleUpsertRequestSchema,
50
+ serializeRunError,
48
51
  snapshotRequestSchema,
49
52
  unsupportedKind,
50
53
  validationError,
51
54
  visibilityStateFromAnswerMentioned,
52
55
  windowCutoff,
53
56
  wordpressEnvSchema
54
- } from "./chunk-FPZUQADO.js";
57
+ } from "./chunk-KWQCQMPY.js";
55
58
  import {
56
59
  IntelligenceService,
57
60
  agentMemory,
@@ -1043,7 +1046,7 @@ async function runRoutes(app, opts) {
1043
1046
  const terminalStatuses = /* @__PURE__ */ new Set(["completed", "partial", "failed", "cancelled"]);
1044
1047
  if (terminalStatuses.has(run.status)) throw runNotCancellable(run.id, run.status);
1045
1048
  const now = (/* @__PURE__ */ new Date()).toISOString();
1046
- app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: "Cancelled by user" }).where(eq7(runs.id, run.id)).run();
1049
+ app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: serializeRunError({ message: "Cancelled by user" }) }).where(eq7(runs.id, run.id)).run();
1047
1050
  writeAuditLog(app.db, {
1048
1051
  projectId: run.projectId,
1049
1052
  actor: "api",
@@ -1080,7 +1083,7 @@ function formatRun(row) {
1080
1083
  location: row.location,
1081
1084
  startedAt: row.startedAt,
1082
1085
  finishedAt: row.finishedAt,
1083
- error: row.error,
1086
+ error: parseRunError(row.error),
1084
1087
  createdAt: row.createdAt
1085
1088
  };
1086
1089
  }
@@ -2255,6 +2258,20 @@ function buildCategoryCounts(counts) {
2255
2258
 
2256
2259
  // ../api-routes/src/intelligence.ts
2257
2260
  import { eq as eq11, desc as desc4, and as and2 } from "drizzle-orm";
2261
+ function emptyHealthSnapshot(projectId) {
2262
+ return {
2263
+ id: `no-data:${projectId}`,
2264
+ projectId,
2265
+ runId: null,
2266
+ overallCitedRate: 0,
2267
+ totalPairs: 0,
2268
+ citedPairs: 0,
2269
+ providerBreakdown: {},
2270
+ createdAt: "",
2271
+ status: "no-data",
2272
+ reason: "no-runs-yet"
2273
+ };
2274
+ }
2258
2275
  function mapInsightRow(r) {
2259
2276
  return {
2260
2277
  id: r.id,
@@ -2280,7 +2297,8 @@ function mapHealthRow(r) {
2280
2297
  totalPairs: r.totalPairs,
2281
2298
  citedPairs: r.citedPairs,
2282
2299
  providerBreakdown: parseJsonColumn(r.providerBreakdown, {}),
2283
- createdAt: r.createdAt
2300
+ createdAt: r.createdAt,
2301
+ status: "ready"
2284
2302
  };
2285
2303
  }
2286
2304
  async function intelligenceRoutes(app) {
@@ -2316,7 +2334,7 @@ async function intelligenceRoutes(app) {
2316
2334
  const project = resolveProject(app.db, request.params.name);
2317
2335
  const row = app.db.select().from(healthSnapshots).where(eq11(healthSnapshots.projectId, project.id)).orderBy(desc4(healthSnapshots.createdAt)).limit(1).get();
2318
2336
  if (!row) {
2319
- throw notFound("Health data for project", request.params.name);
2337
+ return reply.send(emptyHealthSnapshot(project.id));
2320
2338
  }
2321
2339
  return reply.send(mapHealthRow(row));
2322
2340
  });
@@ -4533,10 +4551,11 @@ var routeCatalog = [
4533
4551
  method: "get",
4534
4552
  path: "/api/v1/projects/{name}/health/latest",
4535
4553
  summary: "Get latest health snapshot",
4554
+ description: 'Returns the latest health snapshot. Always 200 once the project exists: when no snapshot exists yet (newly-created project, or only failed runs), the response carries `status: "no-data"` with `reason: "no-runs-yet"` and zeroed metrics. Real snapshots carry `status: "ready"`.',
4536
4555
  tags: ["intelligence"],
4537
4556
  parameters: [nameParameter],
4538
4557
  responses: {
4539
- 200: { description: "Health snapshot returned." },
4558
+ 200: { description: "Health snapshot or no-data sentinel returned." },
4540
4559
  404: { description: "Project not found." }
4541
4560
  }
4542
4561
  },
@@ -10279,7 +10298,7 @@ function mapRunRow(row) {
10279
10298
  location: row.location ?? null,
10280
10299
  startedAt: row.startedAt ?? null,
10281
10300
  finishedAt: row.finishedAt ?? null,
10282
- error: row.error ?? null,
10301
+ error: parseRunError(row.error),
10283
10302
  createdAt: row.createdAt
10284
10303
  };
10285
10304
  }
@@ -13489,10 +13508,10 @@ var JobRunner = class {
13489
13508
  const allFailed = totalSnapshotsInserted === 0 && providerErrors.size > 0;
13490
13509
  const someFailed = providerErrors.size > 0;
13491
13510
  if (allFailed) {
13492
- const errorDetail = JSON.stringify(Object.fromEntries(providerErrors));
13511
+ const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
13493
13512
  this.db.update(runs).set({ status: "failed", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq19(runs.id, runId)).run();
13494
13513
  } else if (someFailed) {
13495
- const errorDetail = JSON.stringify(Object.fromEntries(providerErrors));
13514
+ const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
13496
13515
  this.db.update(runs).set({ status: "partial", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq19(runs.id, runId)).run();
13497
13516
  } else {
13498
13517
  this.db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq19(runs.id, runId)).run();
@@ -15295,7 +15314,7 @@ function buildGetHealthTool(ctx) {
15295
15314
  return {
15296
15315
  name: "get_health",
15297
15316
  label: "Get health",
15298
- description: "Latest visibility health snapshot including overall cited rate, pair counts, and per-provider breakdown.",
15317
+ description: 'Latest visibility health snapshot including overall cited rate, pair counts, and per-provider breakdown. Returns `status: "no-data"` with `reason: "no-runs-yet"` and zeroed metrics for projects with no successful runs yet.',
15299
15318
  parameters: HealthSchema,
15300
15319
  execute: async () => {
15301
15320
  const health = await ctx.client.getHealth(ctx.projectName);
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-SZSWQG3J.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-KWQCQMPY.js";
44
45
  import {
45
46
  apiKeys,
46
47
  competitors,
@@ -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("");
@@ -6689,9 +6701,14 @@ Config saved to ${getConfigPath()}`);
6689
6701
  }
6690
6702
 
6691
6703
  // src/commands/serve.ts
6704
+ function resolveServePort(envPort, configPort) {
6705
+ const trimmed = envPort?.trim();
6706
+ if (trimmed) return parseInt(trimmed, 10);
6707
+ return configPort ?? 4100;
6708
+ }
6692
6709
  async function serveCommand(format = "text") {
6693
6710
  const config = loadConfig();
6694
- const port = parseInt(process.env.CANONRY_PORT ?? "4100", 10);
6711
+ const port = resolveServePort(process.env.CANONRY_PORT, config.port);
6695
6712
  const host = process.env.CANONRY_HOST ?? "127.0.0.1";
6696
6713
  config.port = port;
6697
6714
  const db = createClient(config.database);
@@ -6881,11 +6898,9 @@ function applyServerEnv(values) {
6881
6898
  const port = typeof values.port === "string" ? values.port : void 0;
6882
6899
  const host = typeof values.host === "string" ? values.host : void 0;
6883
6900
  const basePath = typeof values["base-path"] === "string" ? values["base-path"] : void 0;
6884
- process.env.CANONRY_PORT = port ?? "4100";
6901
+ if (port) process.env.CANONRY_PORT = port;
6885
6902
  if (host) process.env.CANONRY_HOST = host;
6886
- else delete process.env.CANONRY_HOST;
6887
6903
  if (basePath) process.env.CANONRY_BASE_PATH = basePath;
6888
- else delete process.env.CANONRY_BASE_PATH;
6889
6904
  }
6890
6905
  var SYSTEM_CLI_COMMANDS = [
6891
6906
  {
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-MGBXRWLX.js";
3
+ } from "./chunk-SZSWQG3J.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-FPZUQADO.js";
6
+ } from "./chunk-KWQCQMPY.js";
7
7
  import "./chunk-PYHANJ3B.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-FPZUQADO.js";
13
+ } from "./chunk-KWQCQMPY.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.1",
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,21 +59,21 @@
59
59
  "@types/node-cron": "^3.0.11",
60
60
  "tsup": "^8.5.1",
61
61
  "tsx": "^4.19.0",
62
- "@ainyc/canonry-api-routes": "0.0.0",
63
62
  "@ainyc/canonry-config": "0.0.0",
64
63
  "@ainyc/canonry-intelligence": "0.0.0",
65
- "@ainyc/canonry-contracts": "0.0.0",
66
64
  "@ainyc/canonry-db": "0.0.0",
65
+ "@ainyc/canonry-api-routes": "0.0.0",
67
66
  "@ainyc/canonry-integration-bing": "0.0.0",
67
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
68
68
  "@ainyc/canonry-integration-google": "0.0.0",
69
+ "@ainyc/canonry-contracts": "0.0.0",
69
70
  "@ainyc/canonry-integration-wordpress": "0.0.0",
70
71
  "@ainyc/canonry-provider-cdp": "0.0.0",
71
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
72
72
  "@ainyc/canonry-provider-claude": "0.0.0",
73
- "@ainyc/canonry-provider-gemini": "0.0.0",
74
73
  "@ainyc/canonry-provider-local": "0.0.0",
75
- "@ainyc/canonry-provider-openai": "0.0.0",
76
- "@ainyc/canonry-provider-perplexity": "0.0.0"
74
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
75
+ "@ainyc/canonry-provider-gemini": "0.0.0",
76
+ "@ainyc/canonry-provider-openai": "0.0.0"
77
77
  },
78
78
  "scripts": {
79
79
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",