@ainyc/canonry 1.44.0 → 1.45.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.
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-BAzKj_9S.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-BsF7MVAu.js"></script>
16
16
  <link rel="stylesheet" crossorigin href="./assets/index--ev1Bjls.css">
17
17
  </head>
18
18
  <body>
@@ -2,6 +2,7 @@ import {
2
2
  IntelligenceService,
3
3
  apiKeys,
4
4
  auditLog,
5
+ bingCoverageSnapshots,
5
6
  bingUrlInspections,
6
7
  competitors,
7
8
  createLogger,
@@ -22,7 +23,7 @@ import {
22
23
  runs,
23
24
  schedules,
24
25
  usageCounters
25
- } from "./chunk-AATIMNOX.js";
26
+ } from "./chunk-SVPQUYTG.js";
26
27
 
27
28
  // src/config.ts
28
29
  import fs from "fs";
@@ -651,6 +652,12 @@ var bingKeywordStatsDtoSchema = z6.object({
651
652
  ctr: z6.number(),
652
653
  averagePosition: z6.number()
653
654
  });
655
+ var bingCoverageSnapshotDtoSchema = z6.object({
656
+ date: z6.string(),
657
+ indexed: z6.number(),
658
+ notIndexed: z6.number(),
659
+ unknown: z6.number()
660
+ });
654
661
  var bingSubmitResultDtoSchema = z6.object({
655
662
  url: z6.string(),
656
663
  status: z6.enum(["success", "error"]),
@@ -4741,6 +4748,18 @@ var routeCatalog = [
4741
4748
  404: { description: "Project not found." }
4742
4749
  }
4743
4750
  },
4751
+ {
4752
+ method: "get",
4753
+ path: "/api/v1/projects/{name}/bing/coverage/history",
4754
+ summary: "Get Bing coverage history snapshots",
4755
+ tags: ["bing"],
4756
+ parameters: [nameParameter, limitQueryParameter],
4757
+ responses: {
4758
+ 200: { description: "Bing coverage history returned." },
4759
+ 400: { description: "Bing is not configured for this project." },
4760
+ 404: { description: "Project not found." }
4761
+ }
4762
+ },
4744
4763
  {
4745
4764
  method: "get",
4746
4765
  path: "/api/v1/projects/{name}/bing/inspections",
@@ -7802,6 +7821,22 @@ async function bingRoutes(app, opts) {
7802
7821
  anchorCount: r.anchorCount ?? null,
7803
7822
  discoveryDate: r.discoveryDate ?? null
7804
7823
  });
7824
+ if (total > 0) {
7825
+ const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7826
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7827
+ app.db.insert(bingCoverageSnapshots).values({
7828
+ id: crypto15.randomUUID(),
7829
+ projectId: project.id,
7830
+ date: snapshotDate,
7831
+ indexed,
7832
+ notIndexed,
7833
+ unknown,
7834
+ createdAt: now
7835
+ }).onConflictDoUpdate({
7836
+ target: [bingCoverageSnapshots.projectId, bingCoverageSnapshots.date],
7837
+ set: { indexed, notIndexed, unknown, createdAt: now }
7838
+ }).run();
7839
+ }
7805
7840
  return {
7806
7841
  summary: {
7807
7842
  total,
@@ -7816,6 +7851,20 @@ async function bingRoutes(app, opts) {
7816
7851
  unknown: unknownUrls.map(formatRow)
7817
7852
  };
7818
7853
  });
7854
+ app.get("/projects/:name/bing/coverage/history", async (request, reply) => {
7855
+ const store = requireConnectionStore(reply);
7856
+ if (!store) return;
7857
+ const project = resolveProject(app.db, request.params.name);
7858
+ const parsed = parseInt(request.query.limit ?? "90", 10);
7859
+ const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
7860
+ const rows = app.db.select().from(bingCoverageSnapshots).where(eq15(bingCoverageSnapshots.projectId, project.id)).orderBy(desc6(bingCoverageSnapshots.date)).limit(limit).all();
7861
+ return rows.map((r) => ({
7862
+ date: r.date,
7863
+ indexed: r.indexed,
7864
+ notIndexed: r.notIndexed,
7865
+ unknown: r.unknown
7866
+ }));
7867
+ });
7819
7868
  app.get("/projects/:name/bing/inspections", async (request, reply) => {
7820
7869
  const store = requireConnectionStore(reply);
7821
7870
  if (!store) return;
@@ -19,6 +19,7 @@ __export(schema_exports, {
19
19
  apiKeys: () => apiKeys,
20
20
  auditLog: () => auditLog,
21
21
  bingConnections: () => bingConnections,
22
+ bingCoverageSnapshots: () => bingCoverageSnapshots,
22
23
  bingKeywordStats: () => bingKeywordStats,
23
24
  bingUrlInspections: () => bingUrlInspections,
24
25
  competitors: () => competitors,
@@ -238,6 +239,17 @@ var gscCoverageSnapshots = sqliteTable("gsc_coverage_snapshots", {
238
239
  index("idx_gsc_coverage_snap_project_date").on(table.projectId, table.date),
239
240
  index("idx_gsc_coverage_snap_run").on(table.syncRunId)
240
241
  ]);
242
+ var bingCoverageSnapshots = sqliteTable("bing_coverage_snapshots", {
243
+ id: text("id").primaryKey(),
244
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
245
+ date: text("date").notNull(),
246
+ indexed: integer("indexed").notNull().default(0),
247
+ notIndexed: integer("not_indexed").notNull().default(0),
248
+ unknown: integer("unknown").notNull().default(0),
249
+ createdAt: text("created_at").notNull()
250
+ }, (table) => [
251
+ uniqueIndex("idx_bing_coverage_snap_project_date").on(table.projectId, table.date)
252
+ ]);
241
253
  var bingConnections = sqliteTable("bing_connections", {
242
254
  id: text("id").primaryKey(),
243
255
  domain: text("domain").notNull(),
@@ -795,7 +807,18 @@ var MIGRATIONS = [
795
807
  )`,
796
808
  `CREATE INDEX IF NOT EXISTS idx_ga_social_ref_project_date ON ga_social_referrals(project_id, date)`,
797
809
  `CREATE INDEX IF NOT EXISTS idx_ga_social_ref_source ON ga_social_referrals(source)`,
798
- `CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_social_ref_unique ON ga_social_referrals(project_id, date, source, medium, channel_group)`
810
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_social_ref_unique ON ga_social_referrals(project_id, date, source, medium, channel_group)`,
811
+ // v26: Bing coverage snapshots for historical tracking (mirrors gsc_coverage_snapshots)
812
+ `CREATE TABLE IF NOT EXISTS bing_coverage_snapshots (
813
+ id TEXT PRIMARY KEY,
814
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
815
+ date TEXT NOT NULL,
816
+ indexed INTEGER NOT NULL DEFAULT 0,
817
+ not_indexed INTEGER NOT NULL DEFAULT 0,
818
+ unknown INTEGER NOT NULL DEFAULT 0,
819
+ created_at TEXT NOT NULL
820
+ )`,
821
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_bing_coverage_snap_project_date ON bing_coverage_snapshots(project_id, date)`
799
822
  ];
800
823
  function isDuplicateColumnError(err) {
801
824
  if (!(err instanceof Error)) return false;
@@ -1239,6 +1262,7 @@ export {
1239
1262
  gscSearchData,
1240
1263
  gscUrlInspections,
1241
1264
  gscCoverageSnapshots,
1265
+ bingCoverageSnapshots,
1242
1266
  bingUrlInspections,
1243
1267
  gaTrafficSnapshots,
1244
1268
  gaAiReferrals,
package/dist/cli.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  setGoogleAuthConfig,
29
29
  showFirstRunNotice,
30
30
  trackEvent
31
- } from "./chunk-C3LF36DQ.js";
31
+ } from "./chunk-B4EP44AR.js";
32
32
  import {
33
33
  apiKeys,
34
34
  competitors,
@@ -38,7 +38,7 @@ import {
38
38
  projects,
39
39
  querySnapshots,
40
40
  runs
41
- } from "./chunk-AATIMNOX.js";
41
+ } from "./chunk-SVPQUYTG.js";
42
42
 
43
43
  // src/cli.ts
44
44
  import { pathToFileURL } from "url";
@@ -355,7 +355,7 @@ async function backfillAnswerVisibilityCommand(opts) {
355
355
  console.log(` Errors: ${providerErrors}`);
356
356
  }
357
357
  async function backfillInsightsCommand(project, opts) {
358
- const { IntelligenceService } = await import("./intelligence-service-36ERONKI.js");
358
+ const { IntelligenceService } = await import("./intelligence-service-TXWOESFH.js");
359
359
  const config = loadConfig();
360
360
  const db = createClient(config.database);
361
361
  migrate(db);
@@ -619,6 +619,7 @@ var ApiClient = class {
619
619
  const serializedBody = body != null ? JSON.stringify(body) : void 0;
620
620
  const headers = {
621
621
  "Authorization": `Bearer ${this.apiKey}`,
622
+ "Accept": "application/json",
622
623
  ...serializedBody != null ? { "Content-Type": "application/json" } : {}
623
624
  };
624
625
  let res;
@@ -860,6 +861,10 @@ var ApiClient = class {
860
861
  async bingCoverage(project) {
861
862
  return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/coverage`);
862
863
  }
864
+ async bingCoverageHistory(project, params) {
865
+ const qs = params?.limit != null ? `?limit=${params.limit}` : "";
866
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/coverage/history${qs}`);
867
+ }
863
868
  async bingInspections(project, params) {
864
869
  const qs = params ? "?" + new URLSearchParams(params).toString() : "";
865
870
  return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/inspections${qs}`);
@@ -1152,6 +1157,26 @@ Bing Index Coverage for "${project}"
1152
1157
  console.log(` Last inspected: ${result.lastInspectedAt}`);
1153
1158
  }
1154
1159
  }
1160
+ async function bingCoverageHistory(project, opts) {
1161
+ const client = getClient();
1162
+ const rows = await client.bingCoverageHistory(project, { limit: opts.limit });
1163
+ if (opts.format === "json") {
1164
+ console.log(JSON.stringify(rows, null, 2));
1165
+ return;
1166
+ }
1167
+ if (rows.length === 0) {
1168
+ console.log('No coverage history found. Run "canonry bing coverage" or "canonry bing refresh" first to capture a snapshot.');
1169
+ return;
1170
+ }
1171
+ console.log(`
1172
+ Bing Coverage History for "${project}" (${rows.length} snapshots):
1173
+ `);
1174
+ console.log(` ${"DATE".padEnd(12)}${"INDEXED".padEnd(10)}${"NOT INDEXED".padEnd(14)}UNKNOWN`);
1175
+ console.log(` ${"\u2500".repeat(12)}${"\u2500".repeat(10)}${"\u2500".repeat(14)}${"\u2500".repeat(10)}`);
1176
+ for (const row of rows) {
1177
+ console.log(` ${row.date.padEnd(12)}${String(row.indexed).padEnd(10)}${String(row.notIndexed).padEnd(14)}${String(row.unknown)}`);
1178
+ }
1179
+ }
1155
1180
  async function bingInspect(project, url, format) {
1156
1181
  const client = getClient();
1157
1182
  const result = await client.bingInspectUrl(project, url);
@@ -1352,6 +1377,22 @@ var BING_CLI_COMMANDS = [
1352
1377
  await bingSetSite(project, siteUrl, input.format);
1353
1378
  }
1354
1379
  },
1380
+ {
1381
+ path: ["bing", "coverage-history"],
1382
+ usage: "canonry bing coverage-history <project> [--limit <n>] [--format json]",
1383
+ options: { limit: stringOption() },
1384
+ run: async (input) => {
1385
+ const project = requireProject(input, "bing.coverage-history", "canonry bing coverage-history <project> [--limit <n>] [--format json]");
1386
+ await bingCoverageHistory(project, {
1387
+ limit: parseIntegerOption(input, "limit", {
1388
+ command: "bing.coverage-history",
1389
+ message: "--limit must be a positive integer",
1390
+ usage: "canonry bing coverage-history <project> [--limit <n>] [--format json]"
1391
+ }),
1392
+ format: input.format
1393
+ });
1394
+ }
1395
+ },
1355
1396
  {
1356
1397
  path: ["bing", "coverage"],
1357
1398
  usage: "canonry bing coverage <project> [--format json]",
@@ -1431,12 +1472,12 @@ var BING_CLI_COMMANDS = [
1431
1472
  },
1432
1473
  {
1433
1474
  path: ["bing"],
1434
- usage: "canonry bing <connect|disconnect|status|sites|set-site|coverage|inspect|inspections|request-indexing|performance|refresh> <project> [args]",
1475
+ usage: "canonry bing <connect|disconnect|status|sites|set-site|coverage|coverage-history|inspect|inspections|request-indexing|performance|refresh> <project> [args]",
1435
1476
  run: async (input) => {
1436
1477
  unknownSubcommand(input.positionals[0], {
1437
1478
  command: "bing",
1438
- usage: "canonry bing <connect|disconnect|status|sites|set-site|coverage|inspect|inspections|request-indexing|performance|refresh> <project> [args]",
1439
- available: ["connect", "disconnect", "status", "sites", "set-site", "coverage", "inspect", "inspections", "request-indexing", "performance", "refresh"]
1479
+ usage: "canonry bing <connect|disconnect|status|sites|set-site|coverage|coverage-history|inspect|inspections|request-indexing|performance|refresh> <project> [args]",
1480
+ available: ["connect", "disconnect", "status", "sites", "set-site", "coverage", "coverage-history", "inspect", "inspections", "request-indexing", "performance", "refresh"]
1440
1481
  });
1441
1482
  }
1442
1483
  }
@@ -4289,7 +4330,7 @@ async function triggerRun(project, opts) {
4289
4330
  console.log(` ${loc} ${id} ${r.status}`);
4290
4331
  }
4291
4332
  if (opts?.wait) {
4292
- const pending = locationRuns.filter((r) => r.id && r.status !== "conflict");
4333
+ const pending = locationRuns.filter((r) => r.id && r.status !== "conflict" && !TERMINAL_STATUSES.has(r.status));
4293
4334
  if (pending.length > 0) {
4294
4335
  process.stderr.write(`Waiting for ${pending.length} run(s)`);
4295
4336
  await Promise.all(
@@ -4309,7 +4350,7 @@ async function triggerRun(project, opts) {
4309
4350
  return;
4310
4351
  }
4311
4352
  const run = response;
4312
- if (opts?.wait) {
4353
+ if (opts?.wait && run.id && !TERMINAL_STATUSES.has(run.status)) {
4313
4354
  process.stderr.write(`Run ${run.id} started`);
4314
4355
  const result = await pollRun(client, run.id);
4315
4356
  if (opts?.format === "json") {
@@ -4320,6 +4361,15 @@ async function triggerRun(project, opts) {
4320
4361
  }
4321
4362
  return;
4322
4363
  }
4364
+ if (opts?.wait && (TERMINAL_STATUSES.has(run.status) || !run.id)) {
4365
+ const result = run.id ? await client.getRun(run.id) : run;
4366
+ if (opts?.format === "json") {
4367
+ console.log(JSON.stringify(result, null, 2));
4368
+ } else {
4369
+ printRunDetail(result);
4370
+ }
4371
+ return;
4372
+ }
4323
4373
  if (opts?.format === "json") {
4324
4374
  console.log(JSON.stringify(run, null, 2));
4325
4375
  return;
@@ -5492,7 +5542,7 @@ var SNAPSHOT_CLI_COMMANDS = [
5492
5542
  // src/commands/insights.ts
5493
5543
  async function listInsights(project, opts) {
5494
5544
  const client = createApiClient();
5495
- const insights = await client.getInsights(project, { dismissed: opts.dismissed });
5545
+ const insights = await client.getInsights(project, { dismissed: opts.dismissed, runId: opts.runId });
5496
5546
  if (opts.format === "json") {
5497
5547
  console.log(JSON.stringify(insights, null, 2));
5498
5548
  return;
@@ -5569,15 +5619,17 @@ async function showHealth(project, opts) {
5569
5619
  var INTELLIGENCE_CLI_COMMANDS = [
5570
5620
  {
5571
5621
  path: ["insights"],
5572
- usage: "canonry insights <project> [--dismissed] [--format json]",
5622
+ usage: "canonry insights <project> [--dismissed] [--run-id <id>] [--format json]",
5573
5623
  options: {
5574
- dismissed: { type: "boolean" }
5624
+ dismissed: { type: "boolean" },
5625
+ "run-id": { type: "string" }
5575
5626
  },
5576
5627
  run: async (input) => {
5577
- const usage = "canonry insights <project> [--dismissed] [--format json]";
5628
+ const usage = "canonry insights <project> [--dismissed] [--run-id <id>] [--format json]";
5578
5629
  const project = requireProject(input, "insights", usage);
5579
5630
  const dismissed = input.values.dismissed === true;
5580
- await listInsights(project, { dismissed, format: input.format });
5631
+ const runId = getString(input.values, "run-id");
5632
+ await listInsights(project, { dismissed, runId, format: input.format });
5581
5633
  }
5582
5634
  },
5583
5635
  {
@@ -5602,8 +5654,11 @@ var INTELLIGENCE_CLI_COMMANDS = [
5602
5654
  const usage = "canonry health <project> [--history] [--limit <n>] [--format json]";
5603
5655
  const project = requireProject(input, "health", usage);
5604
5656
  const history = input.values.history === true;
5605
- const limitStr = getString(input.values, "limit");
5606
- const limit = limitStr ? Number.parseInt(limitStr, 10) : void 0;
5657
+ const limit = parseIntegerOption(input, "limit", {
5658
+ command: "health",
5659
+ usage,
5660
+ message: "--limit must be an integer"
5661
+ });
5607
5662
  await showHealth(project, { history, limit, format: input.format });
5608
5663
  }
5609
5664
  }
@@ -6211,17 +6266,22 @@ async function serveCommand(format = "text") {
6211
6266
  migrate(db);
6212
6267
  const app = await createServer({ config, db });
6213
6268
  let shuttingDown = false;
6214
- const shutdown = () => {
6269
+ const shutdown = (signal) => {
6215
6270
  if (shuttingDown) return;
6216
6271
  shuttingDown = true;
6272
+ if (format === "text") {
6273
+ console.log(`
6274
+ Received ${signal}, stopping server...`);
6275
+ }
6217
6276
  app.close().then(() => {
6218
6277
  process.exit(0);
6219
- }).catch(() => {
6278
+ }).catch((err) => {
6279
+ console.error("Error during shutdown:", err);
6220
6280
  process.exit(1);
6221
6281
  });
6222
6282
  };
6223
- process.on("SIGTERM", shutdown);
6224
- process.on("SIGINT", shutdown);
6283
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
6284
+ process.on("SIGINT", () => shutdown("SIGINT"));
6225
6285
  try {
6226
6286
  await app.listen({ host, port });
6227
6287
  const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-C3LF36DQ.js";
5
- import "./chunk-AATIMNOX.js";
4
+ } from "./chunk-B4EP44AR.js";
5
+ import "./chunk-SVPQUYTG.js";
6
6
  export {
7
7
  createServer,
8
8
  loadConfig
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-AATIMNOX.js";
3
+ } from "./chunk-SVPQUYTG.js";
4
4
  export {
5
5
  IntelligenceService
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "1.44.0",
3
+ "version": "1.45.0",
4
4
  "type": "module",
5
5
  "description": "The ultimate open-source AEO monitoring tool - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -54,19 +54,19 @@
54
54
  "@types/node-cron": "^3.0.11",
55
55
  "tsup": "^8.5.1",
56
56
  "tsx": "^4.19.0",
57
- "@ainyc/canonry-contracts": "0.0.0",
57
+ "@ainyc/canonry-api-routes": "0.0.0",
58
58
  "@ainyc/canonry-config": "0.0.0",
59
+ "@ainyc/canonry-contracts": "0.0.0",
59
60
  "@ainyc/canonry-db": "0.0.0",
60
- "@ainyc/canonry-intelligence": "0.0.0",
61
- "@ainyc/canonry-api-routes": "0.0.0",
62
- "@ainyc/canonry-integration-google": "0.0.0",
63
61
  "@ainyc/canonry-integration-bing": "0.0.0",
62
+ "@ainyc/canonry-intelligence": "0.0.0",
64
63
  "@ainyc/canonry-integration-wordpress": "0.0.0",
64
+ "@ainyc/canonry-integration-google": "0.0.0",
65
65
  "@ainyc/canonry-provider-cdp": "0.0.0",
66
- "@ainyc/canonry-provider-claude": "0.0.0",
67
66
  "@ainyc/canonry-provider-gemini": "0.0.0",
68
- "@ainyc/canonry-provider-openai": "0.0.0",
69
67
  "@ainyc/canonry-provider-local": "0.0.0",
68
+ "@ainyc/canonry-provider-openai": "0.0.0",
69
+ "@ainyc/canonry-provider-claude": "0.0.0",
70
70
  "@ainyc/canonry-provider-perplexity": "0.0.0"
71
71
  },
72
72
  "scripts": {