@ainyc/canonry 4.81.0 → 4.83.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 (23) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -0
  2. package/assets/assets/{BacklinksPage-DHShKKpo.js → BacklinksPage-OrSg_iPA.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-udHScxjY.js → ChartPrimitives-DPBhAT_r.js} +1 -1
  4. package/assets/assets/{ProjectPage-BsS1anh7.js → ProjectPage-CpMcEmtw.js} +1 -1
  5. package/assets/assets/{RunRow-CXyPHMVQ.js → RunRow-2Rty0BAH.js} +1 -1
  6. package/assets/assets/{RunsPage-BpQ_NpFt.js → RunsPage-B3ahqf8s.js} +1 -1
  7. package/assets/assets/{SettingsPage-1ep4ch7n.js → SettingsPage-BIjeI85q.js} +1 -1
  8. package/assets/assets/{TrafficPage-C3Hx-sE7.js → TrafficPage-DjGoj691.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-B26n2R6G.js → TrafficSourceDetailPage-BgKG-2q3.js} +1 -1
  10. package/assets/assets/{arrow-left-Dc_IPJxw.js → arrow-left-Cf7wmru1.js} +1 -1
  11. package/assets/assets/{extract-error-message-B3PoKkHW.js → extract-error-message-CANxezte.js} +1 -1
  12. package/assets/assets/{index-DhdFTQkU.js → index-CGlPx_cu.js} +11 -11
  13. package/assets/assets/{trash-2-BQ69cGl0.js → trash-2-6nHJZrvy.js} +1 -1
  14. package/assets/index.html +1 -1
  15. package/dist/{chunk-GMT3YPLT.js → chunk-BNF3HXBW.js} +25 -1
  16. package/dist/{chunk-UAQ42NVJ.js → chunk-GOWH42QV.js} +50 -2
  17. package/dist/{chunk-VX5C7DK7.js → chunk-NRACXNI7.js} +15 -4
  18. package/dist/{chunk-6XOZSS3Y.js → chunk-Y3O3HBMN.js} +17 -1
  19. package/dist/cli.js +50 -17
  20. package/dist/index.js +4 -4
  21. package/dist/{intelligence-service-CAAQAKPN.js → intelligence-service-FHQM7YMA.js} +2 -2
  22. package/dist/mcp.js +23 -4
  23. package/package.json +5 -5
@@ -1 +1 @@
1
- import{c}from"./index-DhdFTQkU.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-CGlPx_cu.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
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-DhdFTQkU.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-CGlPx_cu.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Dq7p98wZ.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-B57xfQbP.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-ClRVR6aX.js">
@@ -571,6 +571,16 @@ var auditLogEntrySchema = z3.object({
571
571
  createdAt: z3.string()
572
572
  });
573
573
 
574
+ // ../contracts/src/scopes.ts
575
+ var READ_ONLY_SCOPE = "read";
576
+ var WILDCARD_SCOPE = "*";
577
+ function grantsWrite(scope) {
578
+ return scope === WILDCARD_SCOPE || scope === "write" || scope.endsWith(".write");
579
+ }
580
+ function isReadOnlyKey(scopes) {
581
+ return scopes.includes(READ_ONLY_SCOPE) && !scopes.some(grantsWrite);
582
+ }
583
+
574
584
  // ../contracts/src/api-keys.ts
575
585
  import { z as z4 } from "zod";
576
586
  var apiKeyDtoSchema = z4.object({
@@ -579,6 +589,13 @@ var apiKeyDtoSchema = z4.object({
579
589
  /** First 9 chars of the raw token (`cnry_` + 4 hex). Safe to display. */
580
590
  keyPrefix: z4.string(),
581
591
  scopes: z4.array(z4.string()),
592
+ /**
593
+ * Server-derived convenience flag: `true` when this key is read-only (carries
594
+ * the `read` scope and no write-granting scope), in which case the API rejects
595
+ * every write HTTP method for it. Derived from `scopes` via `isReadOnlyKey`
596
+ * — surfaces don't recompute it (see the UI/CLI parity rule). Additive field.
597
+ */
598
+ readOnly: z4.boolean(),
582
599
  createdAt: z4.string(),
583
600
  lastUsedAt: z4.string().nullable(),
584
601
  revokedAt: z4.string().nullable()
@@ -1855,6 +1872,11 @@ var discoveryProbeDtoSchema = z17.object({
1855
1872
  bucket: discoveryBucketSchema.nullable().default(null),
1856
1873
  citationState: citationStateSchema,
1857
1874
  citedDomains: z17.array(z17.string()).default([]),
1875
+ // Answer-text mention signal, independent of citationState. Tri-state: true
1876
+ // (named in the answer prose), false (probed, not named), null (unknown: a
1877
+ // legacy probe written before the engine computed it). Consumers must treat
1878
+ // null as unknown, never as false.
1879
+ answerMentioned: z17.boolean().nullable().default(null),
1858
1880
  createdAt: z17.string()
1859
1881
  });
1860
1882
  var discoverySessionDtoSchema = z17.object({
@@ -4780,5 +4802,7 @@ export {
4780
4802
  AI_ENGINE_SELF_DOMAINS,
4781
4803
  VERTEX_AI_SEARCH_PROXY_DOMAIN,
4782
4804
  AI_PROVIDER_INFRA_DOMAINS,
4783
- escapeLikePattern
4805
+ escapeLikePattern,
4806
+ READ_ONLY_SCOPE,
4807
+ isReadOnlyKey
4784
4808
  };
@@ -156,6 +156,7 @@ import {
156
156
  hasLocationLabel,
157
157
  indexingRequestResponseDtoSchema,
158
158
  internalError,
159
+ isReadOnlyKey,
159
160
  keywordDtoSchema,
160
161
  keywordGenerateRequestSchema,
161
162
  latestProjectRunDtoSchema,
@@ -245,7 +246,7 @@ import {
245
246
  wordpressSchemaDeployResultDtoSchema,
246
247
  wordpressSchemaStatusResultDtoSchema,
247
248
  wordpressStatusDtoSchema
248
- } from "./chunk-GMT3YPLT.js";
249
+ } from "./chunk-BNF3HXBW.js";
249
250
 
250
251
  // src/intelligence-service.ts
251
252
  import { eq as eq36, desc as desc17, asc as asc5, and as and26, ne as ne5, or as or5, inArray as inArray13, gte as gte7, lte as lte4 } from "drizzle-orm";
@@ -1054,6 +1055,10 @@ var discoveryProbes = sqliteTable("discovery_probes", {
1054
1055
  query: text("query").notNull(),
1055
1056
  bucket: text("bucket"),
1056
1057
  citationState: text("citation_state").notNull(),
1058
+ // Answer-text mention signal, independent of citationState. Tri-state: true
1059
+ // (named in the answer prose), false (probed, not named), null (legacy rows
1060
+ // written before this column / never computed). Mirrors querySnapshots.answerMentioned.
1061
+ answerMentioned: integer("answer_mentioned", { mode: "boolean" }),
1057
1062
  citedDomains: text("cited_domains", { mode: "json" }).$type().notNull().default([]),
1058
1063
  rawResponse: text("raw_response"),
1059
1064
  createdAt: text("created_at").notNull()
@@ -3074,6 +3079,16 @@ var MIGRATION_VERSIONS = [
3074
3079
  run: (tx) => {
3075
3080
  addBacklinkSourceDiscriminator(tx);
3076
3081
  }
3082
+ },
3083
+ {
3084
+ // Answer-text mention signal on discovery probes (independent of citation).
3085
+ // Nullable: pre-existing rows were written before the column / never had the
3086
+ // mention computed, so they read back as null (unknown) downstream.
3087
+ version: 79,
3088
+ name: "discovery-probes-answer-mentioned",
3089
+ statements: [
3090
+ `ALTER TABLE discovery_probes ADD COLUMN answer_mentioned INTEGER`
3091
+ ]
3077
3092
  }
3078
3093
  ];
3079
3094
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -5290,6 +5305,7 @@ import fs8 from "fs";
5290
5305
  // ../api-routes/src/auth.ts
5291
5306
  import crypto2 from "crypto";
5292
5307
  import { eq } from "drizzle-orm";
5308
+ var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
5293
5309
  function requireScope(request, scope) {
5294
5310
  const key = request.apiKey;
5295
5311
  if (!key) return;
@@ -5357,6 +5373,9 @@ async function authPlugin(app, opts = {}) {
5357
5373
  app.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(apiKeys.id, key.id)).run();
5358
5374
  const scopes = Array.isArray(key.scopes) ? key.scopes : [];
5359
5375
  request.apiKey = { id: key.id, name: key.name, scopes };
5376
+ if (isReadOnlyKey(scopes) && WRITE_METHODS.has(request.method)) {
5377
+ throw forbidden("This API key is read-only and cannot perform write operations.");
5378
+ }
5360
5379
  });
5361
5380
  }
5362
5381
 
@@ -14431,6 +14450,17 @@ var routeCatalog = [
14431
14450
  200: jsonResponse("Keys returned.", "ApiKeyListDto")
14432
14451
  }
14433
14452
  },
14453
+ {
14454
+ method: "get",
14455
+ path: "/api/v1/keys/self",
14456
+ summary: "Introspect the current API key",
14457
+ description: "Returns SAFE metadata for the key that authenticated this request, including the derived `readOnly` flag. Lets a caller (or the MCP adapter at startup) discover whether its configured key is read-only without listing every key on the instance. Ungated read \u2014 a read-only key can call it.",
14458
+ tags: ["keys"],
14459
+ responses: {
14460
+ 200: jsonResponse("Current key returned.", "ApiKeyDto"),
14461
+ 404: errorResponse("No key on the request (auth skipped).")
14462
+ }
14463
+ },
14434
14464
  {
14435
14465
  method: "post",
14436
14466
  path: "/api/v1/keys",
@@ -17586,11 +17616,13 @@ import crypto12 from "crypto";
17586
17616
  import { desc as desc9, eq as eq17 } from "drizzle-orm";
17587
17617
  var KEYS_WRITE_SCOPE = "keys.write";
17588
17618
  function toApiKeyDto(row) {
17619
+ const scopes = Array.isArray(row.scopes) ? row.scopes : [];
17589
17620
  return {
17590
17621
  id: row.id,
17591
17622
  name: row.name,
17592
17623
  keyPrefix: row.keyPrefix,
17593
- scopes: Array.isArray(row.scopes) ? row.scopes : [],
17624
+ scopes,
17625
+ readOnly: isReadOnlyKey(scopes),
17594
17626
  createdAt: row.createdAt,
17595
17627
  lastUsedAt: row.lastUsedAt ?? null,
17596
17628
  revokedAt: row.revokedAt ?? null
@@ -17601,6 +17633,17 @@ async function keysRoutes(app) {
17601
17633
  const rows = app.db.select().from(apiKeys).orderBy(desc9(apiKeys.createdAt)).all();
17602
17634
  return { keys: rows.map(toApiKeyDto) };
17603
17635
  });
17636
+ app.get("/keys/self", async (request) => {
17637
+ const id = request.apiKey?.id;
17638
+ if (!id) {
17639
+ throw notFound("API key", "self");
17640
+ }
17641
+ const row = app.db.select().from(apiKeys).where(eq17(apiKeys.id, id)).get();
17642
+ if (!row) {
17643
+ throw notFound("API key", id);
17644
+ }
17645
+ return toApiKeyDto(row);
17646
+ });
17604
17647
  app.post("/keys", async (request) => {
17605
17648
  requireScope(request, KEYS_WRITE_SCOPE);
17606
17649
  const parsed = createApiKeyRequestSchema.safeParse(request.body);
@@ -17636,6 +17679,7 @@ async function keysRoutes(app) {
17636
17679
  name,
17637
17680
  keyPrefix,
17638
17681
  scopes: effectiveScopes,
17682
+ readOnly: isReadOnlyKey(effectiveScopes),
17639
17683
  createdAt: now,
17640
17684
  lastUsedAt: null,
17641
17685
  revokedAt: null,
@@ -32749,6 +32793,9 @@ function serializeProbe(row) {
32749
32793
  bucket: bucketParsed?.success ? bucketParsed.data : null,
32750
32794
  citationState: stateParsed.success ? stateParsed.data : "not-cited",
32751
32795
  citedDomains: row.citedDomains,
32796
+ // Boolean-mode column already reads back as boolean | null; a legacy row
32797
+ // written before the column is null (unknown), never coerced to false.
32798
+ answerMentioned: row.answerMentioned ?? null,
32752
32799
  createdAt: row.createdAt
32753
32800
  };
32754
32801
  }
@@ -32865,6 +32912,7 @@ async function executeDiscovery(opts) {
32865
32912
  query,
32866
32913
  bucket,
32867
32914
  citationState: probe.citationState,
32915
+ answerMentioned: probe.answerMentioned,
32868
32916
  citedDomains: probe.citedDomains,
32869
32917
  rawResponse: JSON.stringify(probe.rawResponse),
32870
32918
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-6XOZSS3Y.js";
12
+ } from "./chunk-Y3O3HBMN.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -104,7 +104,7 @@ import {
104
104
  siteAuditPages,
105
105
  siteAuditSnapshots,
106
106
  usageCounters
107
- } from "./chunk-UAQ42NVJ.js";
107
+ } from "./chunk-GOWH42QV.js";
108
108
  import {
109
109
  AGENT_MEMORY_VALUE_MAX_BYTES,
110
110
  AGENT_PROVIDER_IDS,
@@ -160,7 +160,7 @@ import {
160
160
  validationError,
161
161
  winnabilityClassLabel,
162
162
  withRetry
163
- } from "./chunk-GMT3YPLT.js";
163
+ } from "./chunk-BNF3HXBW.js";
164
164
 
165
165
  // src/telemetry.ts
166
166
  import crypto from "crypto";
@@ -5376,9 +5376,14 @@ async function executeDiscoveryRun(opts) {
5376
5376
  canonicalDomain: projectRow.canonicalDomain,
5377
5377
  ownedDomains: projectRow.ownedDomains
5378
5378
  });
5379
+ const brandNames = effectiveBrandNames({
5380
+ displayName: projectRow.displayName,
5381
+ aliases: projectRow.aliases
5382
+ });
5379
5383
  const project = {
5380
5384
  id: projectRow.id,
5381
5385
  name: projectRow.name,
5386
+ brandNames,
5382
5387
  canonicalDomains,
5383
5388
  competitorDomains: projectCompetitors
5384
5389
  };
@@ -5466,9 +5471,15 @@ function buildDefaultDeps(registry) {
5466
5471
  const normalized = adapter.normalizeResult(raw);
5467
5472
  const canonical = new Set(input.project.canonicalDomains.map((d) => d.toLowerCase()));
5468
5473
  const isCited = normalized.citedDomains.some((d) => canonical.has(d.toLowerCase()));
5474
+ const answerMentioned = determineAnswerMentioned(
5475
+ normalized.answerText,
5476
+ input.project.brandNames ?? [],
5477
+ input.project.canonicalDomains
5478
+ );
5469
5479
  return {
5470
5480
  citationState: isCited ? "cited" : "not-cited",
5471
5481
  citedDomains: normalized.citedDomains,
5482
+ answerMentioned,
5472
5483
  rawResponse: raw.rawResponse
5473
5484
  };
5474
5485
  },
@@ -6278,7 +6289,7 @@ function readStoredGroundingSources(rawResponse) {
6278
6289
  return result;
6279
6290
  }
6280
6291
  async function backfillInsightsCommand(project, opts) {
6281
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-CAAQAKPN.js");
6292
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-FHQM7YMA.js");
6282
6293
  const config = loadConfig();
6283
6294
  const db = createClient(config.database);
6284
6295
  migrate(db);
@@ -23,7 +23,7 @@ import {
23
23
  trafficConnectVercelRequestSchema,
24
24
  trafficConnectWordpressRequestSchema,
25
25
  trafficEventKindSchema
26
- } from "./chunk-GMT3YPLT.js";
26
+ } from "./chunk-BNF3HXBW.js";
27
27
 
28
28
  // src/config.ts
29
29
  import fs from "fs";
@@ -1606,6 +1606,18 @@ var postApiV1Keys = (options) => {
1606
1606
  }
1607
1607
  });
1608
1608
  };
1609
+ var getApiV1KeysSelf = (options) => {
1610
+ return (options?.client ?? client).get({
1611
+ security: [
1612
+ {
1613
+ scheme: "bearer",
1614
+ type: "http"
1615
+ }
1616
+ ],
1617
+ url: "/api/v1/keys/self",
1618
+ ...options
1619
+ });
1620
+ };
1609
1621
  var postApiV1KeysByIdRevoke = (options) => {
1610
1622
  return (options.client ?? client).post({
1611
1623
  security: [
@@ -4042,6 +4054,10 @@ var ApiClient = class {
4042
4054
  async listApiKeys() {
4043
4055
  return this.invoke(() => getApiV1Keys({ client: this.heyClient }));
4044
4056
  }
4057
+ /** Introspect the CURRENT key (the one this client authenticates with). */
4058
+ async getApiKeySelf() {
4059
+ return this.invoke(() => getApiV1KeysSelf({ client: this.heyClient }));
4060
+ }
4045
4061
  async createApiKey(body) {
4046
4062
  return this.invoke(() => postApiV1Keys({ client: this.heyClient, body }));
4047
4063
  }
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setTelemetrySource,
28
28
  showFirstRunNotice,
29
29
  trackEvent
30
- } from "./chunk-VX5C7DK7.js";
30
+ } from "./chunk-NRACXNI7.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-6XOZSS3Y.js";
47
+ } from "./chunk-Y3O3HBMN.js";
48
48
  import {
49
49
  apiKeys,
50
50
  createClient,
@@ -52,13 +52,14 @@ import {
52
52
  projects,
53
53
  queries,
54
54
  renderReportHtml
55
- } from "./chunk-UAQ42NVJ.js";
55
+ } from "./chunk-GOWH42QV.js";
56
56
  import {
57
57
  BacklinkSources,
58
58
  CcReleaseSyncStatuses,
59
59
  CheckScopes,
60
60
  CheckStatuses,
61
61
  CitationStates,
62
+ READ_ONLY_SCOPE,
62
63
  RunStatuses,
63
64
  TrafficEventKinds,
64
65
  backlinkSourceSchema,
@@ -75,7 +76,7 @@ import {
75
76
  providerQuotaPolicySchema,
76
77
  resolveProviderInput,
77
78
  winnabilityClassSchema
78
- } from "./chunk-GMT3YPLT.js";
79
+ } from "./chunk-BNF3HXBW.js";
79
80
 
80
81
  // src/cli.ts
81
82
  import { pathToFileURL } from "url";
@@ -1901,11 +1902,12 @@ function printSessionDetail(session) {
1901
1902
  if (session.probes && session.probes.length > 0) {
1902
1903
  const sorted = [...session.probes].sort((a, b) => (a.bucket ?? "").localeCompare(b.bucket ?? ""));
1903
1904
  console.log(`
1904
- Probes (${session.probes.length}):`);
1905
+ Probes (${session.probes.length}): (cell = [citation][mention]; C=cited c=not, M=mentioned m=not, \u2013=no data)`);
1905
1906
  for (const p of sorted) {
1906
1907
  const bucket = (p.bucket ?? "\u2013").padEnd(15);
1907
- const cit = p.citationState === "cited" ? "C" : "c";
1908
- console.log(` [${cit}] ${bucket} ${p.query}`);
1908
+ const citationGlyph = p.citationState === CitationStates.cited ? "C" : "c";
1909
+ const mentionGlyph = typeof p.answerMentioned === "boolean" ? p.answerMentioned ? "M" : "m" : "\u2013";
1910
+ console.log(` [${citationGlyph}${mentionGlyph}] ${bucket} ${p.query}`);
1909
1911
  }
1910
1912
  }
1911
1913
  }
@@ -5640,9 +5642,18 @@ async function listApiKeys(format) {
5640
5642
  }
5641
5643
  }
5642
5644
  async function createApiKey(opts) {
5645
+ const explicitScopes = opts.scopes && opts.scopes.length > 0 ? opts.scopes : void 0;
5646
+ if (opts.readOnly && explicitScopes) {
5647
+ throw new CliError({
5648
+ code: "CLI_USAGE_ERROR",
5649
+ message: "--read-only cannot be combined with --scope",
5650
+ displayMessage: 'Error: --read-only cannot be combined with --scope (it already implies the "read" scope).'
5651
+ });
5652
+ }
5643
5653
  const client = getClient11();
5644
5654
  const body = { name: opts.name };
5645
- if (opts.scopes && opts.scopes.length > 0) body.scopes = opts.scopes;
5655
+ const scopes = opts.readOnly ? [READ_ONLY_SCOPE] : explicitScopes;
5656
+ if (scopes) body.scopes = scopes;
5646
5657
  const created = await client.createApiKey(body);
5647
5658
  if (isMachineFormat(opts.format)) {
5648
5659
  console.log(JSON.stringify(created, null, 2));
@@ -5650,11 +5661,24 @@ async function createApiKey(opts) {
5650
5661
  }
5651
5662
  console.log(`API key "${created.name}" created.
5652
5663
  `);
5653
- console.log(` Key: ${created.key}`);
5654
- console.log(` Prefix: ${created.keyPrefix}`);
5655
- console.log(` Scopes: ${created.scopes.join(", ")}`);
5664
+ console.log(` Key: ${created.key}`);
5665
+ console.log(` Prefix: ${created.keyPrefix}`);
5666
+ console.log(` Scopes: ${created.scopes.join(", ")}`);
5667
+ console.log(` Read-only: ${created.readOnly ? "yes" : "no"}`);
5656
5668
  console.log("\nSave this now \u2014 it will not be shown again.");
5657
5669
  }
5670
+ async function showApiKeySelf(format) {
5671
+ const client = getClient11();
5672
+ const key = await client.getApiKeySelf();
5673
+ if (isMachineFormat(format)) {
5674
+ console.log(JSON.stringify(key, null, 2));
5675
+ return;
5676
+ }
5677
+ console.log(`API key "${key.name}" (${key.keyPrefix})`);
5678
+ console.log(` Scopes: ${key.scopes.join(", ")}`);
5679
+ console.log(` Read-only: ${key.readOnly ? "yes" : "no"}`);
5680
+ console.log(` Status: ${keyStatus(key)}`);
5681
+ }
5658
5682
  async function revokeApiKey(id, format) {
5659
5683
  const client = getClient11();
5660
5684
  const key = await client.revokeApiKey(id);
@@ -5676,15 +5700,16 @@ var KEYS_CLI_COMMANDS = [
5676
5700
  },
5677
5701
  {
5678
5702
  path: ["key", "create"],
5679
- usage: "canonry key create --name <name> [--scope <s> ...] [--format json]",
5703
+ usage: "canonry key create --name <name> [--read-only | --scope <s> ...] [--format json]",
5680
5704
  options: {
5681
5705
  name: stringOption(),
5682
- scope: multiStringOption()
5706
+ scope: multiStringOption(),
5707
+ "read-only": { type: "boolean" }
5683
5708
  },
5684
5709
  run: async (input) => {
5685
5710
  const name = requireStringOption(input, "name", {
5686
5711
  command: "key.create",
5687
- usage: "canonry key create --name <name> [--scope <s> ...] [--format json]",
5712
+ usage: "canonry key create --name <name> [--read-only | --scope <s> ...] [--format json]",
5688
5713
  message: "--name is required"
5689
5714
  });
5690
5715
  const raw = getStringArray(input.values, "scope") ?? [];
@@ -5692,10 +5717,18 @@ var KEYS_CLI_COMMANDS = [
5692
5717
  await createApiKey({
5693
5718
  name,
5694
5719
  scopes: scopes.length > 0 ? scopes : void 0,
5720
+ readOnly: getBoolean(input.values, "read-only"),
5695
5721
  format: input.format
5696
5722
  });
5697
5723
  }
5698
5724
  },
5725
+ {
5726
+ path: ["key", "whoami"],
5727
+ usage: "canonry key whoami [--format json]",
5728
+ run: async (input) => {
5729
+ await showApiKeySelf(input.format);
5730
+ }
5731
+ },
5699
5732
  {
5700
5733
  path: ["key", "revoke"],
5701
5734
  usage: "canonry key revoke <id> [--format json]",
@@ -5710,12 +5743,12 @@ var KEYS_CLI_COMMANDS = [
5710
5743
  },
5711
5744
  {
5712
5745
  path: ["key"],
5713
- usage: "canonry key <list|create|revoke>",
5746
+ usage: "canonry key <list|create|revoke|whoami>",
5714
5747
  run: async (input) => {
5715
5748
  unknownSubcommand(input.positionals[0], {
5716
5749
  command: "key",
5717
- usage: "canonry key <list|create|revoke>",
5718
- available: ["list", "create", "revoke"]
5750
+ usage: "canonry key <list|create|revoke|whoami>",
5751
+ available: ["list", "create", "revoke", "whoami"]
5719
5752
  });
5720
5753
  }
5721
5754
  }
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-VX5C7DK7.js";
3
+ } from "./chunk-NRACXNI7.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-6XOZSS3Y.js";
7
- import "./chunk-UAQ42NVJ.js";
8
- import "./chunk-GMT3YPLT.js";
6
+ } from "./chunk-Y3O3HBMN.js";
7
+ import "./chunk-GOWH42QV.js";
8
+ import "./chunk-BNF3HXBW.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-UAQ42NVJ.js";
4
- import "./chunk-GMT3YPLT.js";
3
+ } from "./chunk-GOWH42QV.js";
4
+ import "./chunk-BNF3HXBW.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-6XOZSS3Y.js";
7
- import "./chunk-GMT3YPLT.js";
6
+ } from "./chunk-Y3O3HBMN.js";
7
+ import {
8
+ isReadOnlyKey
9
+ } from "./chunk-BNF3HXBW.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.81.0",
3
+ "version": "4.83.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,19 +62,19 @@
62
62
  "@types/node-cron": "^3.0.11",
63
63
  "tsup": "^8.5.1",
64
64
  "tsx": "^4.19.0",
65
+ "@ainyc/canonry-config": "0.0.0",
65
66
  "@ainyc/canonry-api-client": "0.0.0",
66
67
  "@ainyc/canonry-api-routes": "0.0.0",
67
- "@ainyc/canonry-config": "0.0.0",
68
68
  "@ainyc/canonry-contracts": "0.0.0",
69
69
  "@ainyc/canonry-db": "0.0.0",
70
- "@ainyc/canonry-integration-bing": "0.0.0",
71
70
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
72
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
73
71
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
72
+ "@ainyc/canonry-integration-bing": "0.0.0",
73
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
74
74
  "@ainyc/canonry-integration-google": "0.0.0",
75
+ "@ainyc/canonry-integration-google-places": "0.0.0",
75
76
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
76
77
  "@ainyc/canonry-integration-traffic": "0.0.0",
77
- "@ainyc/canonry-integration-google-places": "0.0.0",
78
78
  "@ainyc/canonry-integration-wordpress": "0.0.0",
79
79
  "@ainyc/canonry-provider-cdp": "0.0.0",
80
80
  "@ainyc/canonry-provider-claude": "0.0.0",