@ainyc/canonry 4.80.0 → 4.82.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.
Files changed (25) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +24 -12
  2. package/assets/assets/{BacklinksPage-dRc62jAY.js → BacklinksPage-CHclt-pq.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-D2_IvTkk.js → ChartPrimitives-2Ub4vNWe.js} +1 -1
  4. package/assets/assets/ProjectPage-UPmHfuxR.js +6 -0
  5. package/assets/assets/{RunRow-C0MA3yuQ.js → RunRow-rUL1UeA3.js} +1 -1
  6. package/assets/assets/{RunsPage-4uxTYgGy.js → RunsPage-BQpHfUJf.js} +1 -1
  7. package/assets/assets/{SettingsPage-3-SLhcJ7.js → SettingsPage-DjTJlr_1.js} +1 -1
  8. package/assets/assets/{TrafficPage-DZ50qwme.js → TrafficPage-D7rv3BrH.js} +1 -1
  9. package/assets/assets/TrafficSourceDetailPage-BysyuH2H.js +1 -0
  10. package/assets/assets/{arrow-left-BaZIkAXX.js → arrow-left-CR_FGlkE.js} +1 -1
  11. package/assets/assets/{extract-error-message-cpvfuFqW.js → extract-error-message-BKkAbWNp.js} +1 -1
  12. package/assets/assets/{index-EnY_OBRd.js → index-DzzTt20n.js} +87 -87
  13. package/assets/assets/{trash-2-JpcztiS5.js → trash-2-uSttujvh.js} +1 -1
  14. package/assets/index.html +1 -1
  15. package/dist/{chunk-CXIGHPBE.js → chunk-IEUTAQUF.js} +471 -124
  16. package/dist/{chunk-2QBSRHSN.js → chunk-JLAD6CYH.js} +88 -8
  17. package/dist/{chunk-AVN6Q6LM.js → chunk-KPSFRSS7.js} +96 -3
  18. package/dist/{chunk-LCABGFYN.js → chunk-NSZ3D3MM.js} +404 -242
  19. package/dist/cli.js +145 -18
  20. package/dist/index.js +4 -4
  21. package/dist/{intelligence-service-ZWW3I3NL.js → intelligence-service-2UUJ3YGI.js} +2 -2
  22. package/dist/mcp.js +23 -4
  23. package/package.json +8 -8
  24. package/assets/assets/ProjectPage-DSuvRUIf.js +0 -6
  25. package/assets/assets/TrafficSourceDetailPage-CzK5TMFp.js +0 -1
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setTelemetrySource,
28
28
  showFirstRunNotice,
29
29
  trackEvent
30
- } from "./chunk-LCABGFYN.js";
30
+ } from "./chunk-NSZ3D3MM.js";
31
31
  import {
32
32
  CliError,
33
33
  EXIT_SYSTEM_ERROR,
@@ -44,7 +44,7 @@ import {
44
44
  saveConfig,
45
45
  saveConfigPatch,
46
46
  usageError
47
- } from "./chunk-2QBSRHSN.js";
47
+ } from "./chunk-JLAD6CYH.js";
48
48
  import {
49
49
  apiKeys,
50
50
  createClient,
@@ -52,14 +52,17 @@ import {
52
52
  projects,
53
53
  queries,
54
54
  renderReportHtml
55
- } from "./chunk-CXIGHPBE.js";
55
+ } from "./chunk-IEUTAQUF.js";
56
56
  import {
57
+ BacklinkSources,
57
58
  CcReleaseSyncStatuses,
58
59
  CheckScopes,
59
60
  CheckStatuses,
60
61
  CitationStates,
62
+ READ_ONLY_SCOPE,
61
63
  RunStatuses,
62
64
  TrafficEventKinds,
65
+ backlinkSourceSchema,
63
66
  discoveryBucketSchema,
64
67
  discoveryCompetitorTypeSchema,
65
68
  effectiveDomains,
@@ -73,7 +76,7 @@ import {
73
76
  providerQuotaPolicySchema,
74
77
  resolveProviderInput,
75
78
  winnabilityClassSchema
76
- } from "./chunk-AVN6Q6LM.js";
79
+ } from "./chunk-KPSFRSS7.js";
77
80
 
78
81
  // src/cli.ts
79
82
  import { pathToFileURL } from "url";
@@ -441,6 +444,17 @@ function emitJsonl(records) {
441
444
  }
442
445
 
443
446
  // src/commands/backlinks.ts
447
+ function parseSourceFlag(value) {
448
+ if (value === void 0) return void 0;
449
+ const parsed = backlinkSourceSchema.safeParse(value);
450
+ if (!parsed.success) {
451
+ throw new CliError({
452
+ code: "VALIDATION_ERROR",
453
+ message: `Invalid --source "${value}". Expected one of: ${Object.values(BacklinkSources).join(", ")}.`
454
+ });
455
+ }
456
+ return parsed.data;
457
+ }
444
458
  function getClient() {
445
459
  return createApiClient();
446
460
  }
@@ -472,8 +486,10 @@ function formatSync(sync) {
472
486
  function formatSummaryAndDomains(project, response) {
473
487
  const lines = [];
474
488
  lines.push(`Project: ${project}`);
489
+ lines.push(`Source: ${response.source}`);
475
490
  if (!response.summary) {
476
- lines.push("No ready release \u2014 run `canonry backlinks sync --release <id>` first.");
491
+ const hint = response.source === BacklinkSources["bing-webmaster"] ? "No Bing inbound links yet \u2014 run `canonry backlinks bing-sync <project>` (Bing must be connected)." : "No ready release \u2014 run `canonry backlinks sync --release <id>` first.";
492
+ lines.push(hint);
477
493
  return lines.join("\n");
478
494
  }
479
495
  const s = response.summary;
@@ -619,7 +635,8 @@ async function backlinksList(opts) {
619
635
  const response = await client.backlinksDomains(opts.project, {
620
636
  limit: opts.limit ?? 50,
621
637
  release: opts.release,
622
- excludeCrawlers: opts.excludeCrawlers
638
+ excludeCrawlers: opts.excludeCrawlers,
639
+ source: opts.source
623
640
  });
624
641
  if (opts.format === "json") {
625
642
  printJson(response);
@@ -632,6 +649,45 @@ async function backlinksList(opts) {
632
649
  }
633
650
  console.log(formatSummaryAndDomains(opts.project, response));
634
651
  }
652
+ function formatSourceAvailability(res) {
653
+ const lines = [];
654
+ lines.push(`Project: ${res.projectId} Target: ${res.targetDomain}`);
655
+ lines.push("");
656
+ lines.push("Source Connected Data Linking domains Latest window");
657
+ for (const s of res.sources) {
658
+ lines.push(
659
+ `${s.source.padEnd(15)} ${(s.connected ? "yes" : "no").padEnd(10)} ${(s.hasData ? "yes" : "no").padEnd(6)} ${String(s.totalLinkingDomains).padStart(15)} ${s.latestRelease ?? "\u2014"}`
660
+ );
661
+ }
662
+ if (!res.anyConnected) {
663
+ lines.push("");
664
+ lines.push("No backlink source is set up. Enable Common Crawl (`canonry project \u2026 autoExtractBacklinks`");
665
+ lines.push("+ `canonry backlinks sync`) or connect Bing (`canonry bing connect <project> --api-key <key>`).");
666
+ }
667
+ return lines.join("\n");
668
+ }
669
+ async function backlinksSources(opts) {
670
+ const res = await getClient().backlinksSources(opts.project, { excludeCrawlers: opts.excludeCrawlers });
671
+ if (opts.format === "json") {
672
+ printJson(res);
673
+ return;
674
+ } else if (opts.format === "jsonl") {
675
+ emitJsonl(res.sources.map((s) => ({ project: opts.project, targetDomain: res.targetDomain, ...s })));
676
+ return;
677
+ }
678
+ console.log(formatSourceAvailability(res));
679
+ }
680
+ async function backlinksBingSync(opts) {
681
+ const client = getClient();
682
+ const run = await client.backlinksBingSync(opts.project);
683
+ const final = opts.wait ? await pollRun(run.id, opts.format) : run;
684
+ if (isMachineFormat(opts.format)) {
685
+ printJson(final);
686
+ return;
687
+ }
688
+ if (opts.wait) process.stderr.write("\n");
689
+ console.log(`Bing sync run ${final.id} (${final.status})${final.error ? " \u2014 " + formatRunErrorOneLine(final.error) : ""}`);
690
+ }
635
691
  async function backlinksReleases(opts = {}) {
636
692
  const rows = await getClient().backlinksCachedReleases();
637
693
  if (opts.format === "json") {
@@ -709,17 +765,18 @@ var BACKLINKS_CLI_COMMANDS = [
709
765
  },
710
766
  {
711
767
  path: ["backlinks", "list"],
712
- usage: "canonry backlinks list <project> [--limit <n>] [--release <id>] [--exclude-crawlers] [--format json]",
768
+ usage: "canonry backlinks list <project> [--source commoncrawl|bing-webmaster] [--limit <n>] [--release <id>] [--exclude-crawlers] [--format json|jsonl]",
713
769
  options: {
714
770
  limit: stringOption(),
715
771
  release: stringOption(),
772
+ source: stringOption(),
716
773
  "exclude-crawlers": { type: "boolean" }
717
774
  },
718
775
  run: async (input) => {
719
776
  const project = requireProject(
720
777
  input,
721
778
  "backlinks list",
722
- "canonry backlinks list <project> [--limit <n>] [--release <id>] [--exclude-crawlers]"
779
+ "canonry backlinks list <project> [--source commoncrawl|bing-webmaster] [--limit <n>] [--release <id>] [--exclude-crawlers]"
723
780
  );
724
781
  const limit = parseIntegerOption(input, "limit", {
725
782
  message: "--limit must be an integer",
@@ -730,11 +787,50 @@ var BACKLINKS_CLI_COMMANDS = [
730
787
  project,
731
788
  limit,
732
789
  release: getString(input.values, "release"),
790
+ source: parseSourceFlag(getString(input.values, "source")),
791
+ excludeCrawlers: getBoolean(input.values, "exclude-crawlers"),
792
+ format: input.format
793
+ });
794
+ }
795
+ },
796
+ {
797
+ path: ["backlinks", "sources"],
798
+ usage: "canonry backlinks sources <project> [--exclude-crawlers] [--format json|jsonl]",
799
+ options: {
800
+ "exclude-crawlers": { type: "boolean" }
801
+ },
802
+ run: async (input) => {
803
+ const project = requireProject(
804
+ input,
805
+ "backlinks sources",
806
+ "canonry backlinks sources <project>"
807
+ );
808
+ await backlinksSources({
809
+ project,
733
810
  excludeCrawlers: getBoolean(input.values, "exclude-crawlers"),
734
811
  format: input.format
735
812
  });
736
813
  }
737
814
  },
815
+ {
816
+ path: ["backlinks", "bing-sync"],
817
+ usage: "canonry backlinks bing-sync <project> [--wait] [--format json]",
818
+ options: {
819
+ wait: { type: "boolean" }
820
+ },
821
+ run: async (input) => {
822
+ const project = requireProject(
823
+ input,
824
+ "backlinks bing-sync",
825
+ "canonry backlinks bing-sync <project> [--wait]"
826
+ );
827
+ await backlinksBingSync({
828
+ project,
829
+ wait: getBoolean(input.values, "wait"),
830
+ format: input.format
831
+ });
832
+ }
833
+ },
738
834
  {
739
835
  path: ["backlinks", "releases"],
740
836
  usage: "canonry backlinks releases [--format json]",
@@ -5545,9 +5641,18 @@ async function listApiKeys(format) {
5545
5641
  }
5546
5642
  }
5547
5643
  async function createApiKey(opts) {
5644
+ const explicitScopes = opts.scopes && opts.scopes.length > 0 ? opts.scopes : void 0;
5645
+ if (opts.readOnly && explicitScopes) {
5646
+ throw new CliError({
5647
+ code: "CLI_USAGE_ERROR",
5648
+ message: "--read-only cannot be combined with --scope",
5649
+ displayMessage: 'Error: --read-only cannot be combined with --scope (it already implies the "read" scope).'
5650
+ });
5651
+ }
5548
5652
  const client = getClient11();
5549
5653
  const body = { name: opts.name };
5550
- if (opts.scopes && opts.scopes.length > 0) body.scopes = opts.scopes;
5654
+ const scopes = opts.readOnly ? [READ_ONLY_SCOPE] : explicitScopes;
5655
+ if (scopes) body.scopes = scopes;
5551
5656
  const created = await client.createApiKey(body);
5552
5657
  if (isMachineFormat(opts.format)) {
5553
5658
  console.log(JSON.stringify(created, null, 2));
@@ -5555,11 +5660,24 @@ async function createApiKey(opts) {
5555
5660
  }
5556
5661
  console.log(`API key "${created.name}" created.
5557
5662
  `);
5558
- console.log(` Key: ${created.key}`);
5559
- console.log(` Prefix: ${created.keyPrefix}`);
5560
- console.log(` Scopes: ${created.scopes.join(", ")}`);
5663
+ console.log(` Key: ${created.key}`);
5664
+ console.log(` Prefix: ${created.keyPrefix}`);
5665
+ console.log(` Scopes: ${created.scopes.join(", ")}`);
5666
+ console.log(` Read-only: ${created.readOnly ? "yes" : "no"}`);
5561
5667
  console.log("\nSave this now \u2014 it will not be shown again.");
5562
5668
  }
5669
+ async function showApiKeySelf(format) {
5670
+ const client = getClient11();
5671
+ const key = await client.getApiKeySelf();
5672
+ if (isMachineFormat(format)) {
5673
+ console.log(JSON.stringify(key, null, 2));
5674
+ return;
5675
+ }
5676
+ console.log(`API key "${key.name}" (${key.keyPrefix})`);
5677
+ console.log(` Scopes: ${key.scopes.join(", ")}`);
5678
+ console.log(` Read-only: ${key.readOnly ? "yes" : "no"}`);
5679
+ console.log(` Status: ${keyStatus(key)}`);
5680
+ }
5563
5681
  async function revokeApiKey(id, format) {
5564
5682
  const client = getClient11();
5565
5683
  const key = await client.revokeApiKey(id);
@@ -5581,15 +5699,16 @@ var KEYS_CLI_COMMANDS = [
5581
5699
  },
5582
5700
  {
5583
5701
  path: ["key", "create"],
5584
- usage: "canonry key create --name <name> [--scope <s> ...] [--format json]",
5702
+ usage: "canonry key create --name <name> [--read-only | --scope <s> ...] [--format json]",
5585
5703
  options: {
5586
5704
  name: stringOption(),
5587
- scope: multiStringOption()
5705
+ scope: multiStringOption(),
5706
+ "read-only": { type: "boolean" }
5588
5707
  },
5589
5708
  run: async (input) => {
5590
5709
  const name = requireStringOption(input, "name", {
5591
5710
  command: "key.create",
5592
- usage: "canonry key create --name <name> [--scope <s> ...] [--format json]",
5711
+ usage: "canonry key create --name <name> [--read-only | --scope <s> ...] [--format json]",
5593
5712
  message: "--name is required"
5594
5713
  });
5595
5714
  const raw = getStringArray(input.values, "scope") ?? [];
@@ -5597,10 +5716,18 @@ var KEYS_CLI_COMMANDS = [
5597
5716
  await createApiKey({
5598
5717
  name,
5599
5718
  scopes: scopes.length > 0 ? scopes : void 0,
5719
+ readOnly: getBoolean(input.values, "read-only"),
5600
5720
  format: input.format
5601
5721
  });
5602
5722
  }
5603
5723
  },
5724
+ {
5725
+ path: ["key", "whoami"],
5726
+ usage: "canonry key whoami [--format json]",
5727
+ run: async (input) => {
5728
+ await showApiKeySelf(input.format);
5729
+ }
5730
+ },
5604
5731
  {
5605
5732
  path: ["key", "revoke"],
5606
5733
  usage: "canonry key revoke <id> [--format json]",
@@ -5615,12 +5742,12 @@ var KEYS_CLI_COMMANDS = [
5615
5742
  },
5616
5743
  {
5617
5744
  path: ["key"],
5618
- usage: "canonry key <list|create|revoke>",
5745
+ usage: "canonry key <list|create|revoke|whoami>",
5619
5746
  run: async (input) => {
5620
5747
  unknownSubcommand(input.positionals[0], {
5621
5748
  command: "key",
5622
- usage: "canonry key <list|create|revoke>",
5623
- available: ["list", "create", "revoke"]
5749
+ usage: "canonry key <list|create|revoke|whoami>",
5750
+ available: ["list", "create", "revoke", "whoami"]
5624
5751
  });
5625
5752
  }
5626
5753
  }
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-LCABGFYN.js";
3
+ } from "./chunk-NSZ3D3MM.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-2QBSRHSN.js";
7
- import "./chunk-CXIGHPBE.js";
8
- import "./chunk-AVN6Q6LM.js";
6
+ } from "./chunk-JLAD6CYH.js";
7
+ import "./chunk-IEUTAQUF.js";
8
+ import "./chunk-KPSFRSS7.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-CXIGHPBE.js";
4
- import "./chunk-AVN6Q6LM.js";
3
+ } from "./chunk-IEUTAQUF.js";
4
+ import "./chunk-KPSFRSS7.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,8 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-2QBSRHSN.js";
7
- import "./chunk-AVN6Q6LM.js";
6
+ } from "./chunk-JLAD6CYH.js";
7
+ import {
8
+ isReadOnlyKey
9
+ } from "./chunk-KPSFRSS7.js";
8
10
 
9
11
  // src/mcp/cli.ts
10
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -363,9 +365,25 @@ async function main(argv = process.argv.slice(2)) {
363
365
  }
364
366
  throw error;
365
367
  }
366
- const server = createCanonryMcpServer({ scope: options.scope, eager: options.eager });
368
+ const client = createApiClient();
369
+ const scope = await resolveEffectiveScope(client, options.scope);
370
+ const server = createCanonryMcpServer({ scope, eager: options.eager, clientFactory: () => client });
367
371
  await server.connect(new StdioServerTransport());
368
372
  }
373
+ async function resolveEffectiveScope(client, flagScope) {
374
+ if (flagScope === "read-only") return "read-only";
375
+ try {
376
+ const self = await client.getApiKeySelf();
377
+ if (isReadOnlyKey(self.scopes)) {
378
+ process.stderr.write(
379
+ "canonry-mcp: configured API key is read-only \u2014 restricting to read tools.\n"
380
+ );
381
+ return "read-only";
382
+ }
383
+ } catch {
384
+ }
385
+ return flagScope;
386
+ }
369
387
  function parseCliOptions(argv, env = process.env) {
370
388
  if (argv.includes("--help") || argv.includes("-h")) {
371
389
  throw new HelpRequested();
@@ -419,5 +437,6 @@ export {
419
437
  HELP_TEXT,
420
438
  HelpRequested,
421
439
  main,
422
- parseCliOptions
440
+ parseCliOptions,
441
+ resolveEffectiveScope
423
442
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.80.0",
3
+ "version": "4.82.0",
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",
@@ -62,27 +62,27 @@
62
62
  "@types/node-cron": "^3.0.11",
63
63
  "tsup": "^8.5.1",
64
64
  "tsx": "^4.19.0",
65
+ "@ainyc/canonry-api-routes": "0.0.0",
66
+ "@ainyc/canonry-api-client": "0.0.0",
65
67
  "@ainyc/canonry-config": "0.0.0",
68
+ "@ainyc/canonry-db": "0.0.0",
66
69
  "@ainyc/canonry-contracts": "0.0.0",
67
- "@ainyc/canonry-api-client": "0.0.0",
68
- "@ainyc/canonry-api-routes": "0.0.0",
69
70
  "@ainyc/canonry-integration-bing": "0.0.0",
70
- "@ainyc/canonry-db": "0.0.0",
71
71
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
72
72
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
73
73
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
74
74
  "@ainyc/canonry-integration-google": "0.0.0",
75
75
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
76
- "@ainyc/canonry-integration-wordpress": "0.0.0",
77
- "@ainyc/canonry-intelligence": "0.0.0",
78
76
  "@ainyc/canonry-integration-google-places": "0.0.0",
79
77
  "@ainyc/canonry-integration-traffic": "0.0.0",
78
+ "@ainyc/canonry-intelligence": "0.0.0",
80
79
  "@ainyc/canonry-provider-cdp": "0.0.0",
80
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
81
81
  "@ainyc/canonry-provider-claude": "0.0.0",
82
82
  "@ainyc/canonry-provider-gemini": "0.0.0",
83
83
  "@ainyc/canonry-provider-local": "0.0.0",
84
- "@ainyc/canonry-provider-openai": "0.0.0",
85
- "@ainyc/canonry-provider-perplexity": "0.0.0"
84
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
85
+ "@ainyc/canonry-provider-openai": "0.0.0"
86
86
  },
87
87
  "scripts": {
88
88
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",