@ainyc/canonry 1.44.0 → 1.45.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-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-WBV2D7FB.js";
26
27
 
27
28
  // src/config.ts
28
29
  import fs from "fs";
@@ -267,7 +268,7 @@ import crypto22 from "crypto";
267
268
  import fs5 from "fs";
268
269
  import path6 from "path";
269
270
  import { fileURLToPath } from "url";
270
- import { eq as eq23 } from "drizzle-orm";
271
+ import { eq as eq23, sql as sql6 } from "drizzle-orm";
271
272
  import Fastify from "fastify";
272
273
 
273
274
  // ../contracts/src/config-schema.ts
@@ -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;
@@ -15036,6 +15085,70 @@ function serializeSessionCookie(opts) {
15036
15085
  }
15037
15086
  return parts.join("; ");
15038
15087
  }
15088
+ function migrateDbCredentialsToConfig(db, config) {
15089
+ try {
15090
+ const googleColCheck = db.all(sql6.raw(
15091
+ `SELECT COUNT(*) as c FROM pragma_table_info('google_connections') WHERE name = 'access_token'`
15092
+ ));
15093
+ if (googleColCheck[0]?.c) {
15094
+ const rows = db.all(sql6.raw(
15095
+ `SELECT domain, connection_type, property_id, sitemap_url, access_token, refresh_token, token_expires_at, scopes, created_at, updated_at FROM google_connections WHERE refresh_token IS NOT NULL AND refresh_token != ''`
15096
+ ));
15097
+ let migrated = 0;
15098
+ for (const row of rows) {
15099
+ const connType = row.connection_type;
15100
+ const existing = getGoogleConnection(config, row.domain, connType);
15101
+ if (existing?.refreshToken) continue;
15102
+ upsertGoogleConnection(config, {
15103
+ domain: row.domain,
15104
+ connectionType: connType,
15105
+ propertyId: row.property_id ?? null,
15106
+ sitemapUrl: row.sitemap_url ?? null,
15107
+ accessToken: row.access_token ?? void 0,
15108
+ refreshToken: row.refresh_token ?? null,
15109
+ tokenExpiresAt: row.token_expires_at ?? null,
15110
+ scopes: parseJsonColumn(row.scopes, []),
15111
+ createdAt: row.created_at,
15112
+ updatedAt: row.updated_at
15113
+ });
15114
+ migrated++;
15115
+ }
15116
+ if (migrated > 0) {
15117
+ saveConfigPatch({ google: config.google });
15118
+ log8.info("credentials.migrated", { type: "google", count: migrated });
15119
+ }
15120
+ }
15121
+ const gaColCheck = db.all(sql6.raw(
15122
+ `SELECT COUNT(*) as c FROM pragma_table_info('ga_connections') WHERE name = 'private_key'`
15123
+ ));
15124
+ if (gaColCheck[0]?.c) {
15125
+ const rows = db.all(sql6.raw(
15126
+ `SELECT id, project_id, property_id, client_email, private_key, created_at, updated_at FROM ga_connections WHERE private_key IS NOT NULL AND private_key != ''`
15127
+ ));
15128
+ let migrated = 0;
15129
+ for (const row of rows) {
15130
+ const project = db.select({ name: projects.name }).from(projects).where(eq23(projects.id, row.project_id)).get();
15131
+ if (!project) continue;
15132
+ const existing = getGa4Connection(config, project.name);
15133
+ if (existing?.privateKey) continue;
15134
+ upsertGa4Connection(config, {
15135
+ projectName: project.name,
15136
+ propertyId: row.property_id,
15137
+ clientEmail: row.client_email,
15138
+ privateKey: row.private_key,
15139
+ createdAt: row.created_at,
15140
+ updatedAt: row.updated_at
15141
+ });
15142
+ migrated++;
15143
+ }
15144
+ if (migrated > 0) {
15145
+ saveConfigPatch({ ga4: config.ga4 });
15146
+ log8.info("credentials.migrated", { type: "ga4", count: migrated });
15147
+ }
15148
+ }
15149
+ } catch {
15150
+ }
15151
+ }
15039
15152
  async function createServer(opts) {
15040
15153
  const logger = opts.logger === false ? false : process.stdout.isTTY ? {
15041
15154
  transport: {
@@ -15060,6 +15173,7 @@ async function createServer(opts) {
15060
15173
  quota: opts.config.geminiQuota
15061
15174
  };
15062
15175
  }
15176
+ migrateDbCredentialsToConfig(opts.db, opts.config);
15063
15177
  log8.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
15064
15178
  const p = providers[k];
15065
15179
  return p?.apiKey || p?.baseUrl || p?.vertexProject;
@@ -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,
@@ -174,10 +175,6 @@ var googleConnections = sqliteTable("google_connections", {
174
175
  connectionType: text("connection_type").notNull(),
175
176
  propertyId: text("property_id"),
176
177
  sitemapUrl: text("sitemap_url"),
177
- // WARNING: Authentication material should be stored in config.yaml per CLAUDE.md
178
- accessToken: text("access_token"),
179
- refreshToken: text("refresh_token"),
180
- tokenExpiresAt: text("token_expires_at"),
181
178
  scopes: text("scopes").notNull().default("[]"),
182
179
  createdAt: text("created_at").notNull(),
183
180
  updatedAt: text("updated_at").notNull()
@@ -238,6 +235,17 @@ var gscCoverageSnapshots = sqliteTable("gsc_coverage_snapshots", {
238
235
  index("idx_gsc_coverage_snap_project_date").on(table.projectId, table.date),
239
236
  index("idx_gsc_coverage_snap_run").on(table.syncRunId)
240
237
  ]);
238
+ var bingCoverageSnapshots = sqliteTable("bing_coverage_snapshots", {
239
+ id: text("id").primaryKey(),
240
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
241
+ date: text("date").notNull(),
242
+ indexed: integer("indexed").notNull().default(0),
243
+ notIndexed: integer("not_indexed").notNull().default(0),
244
+ unknown: integer("unknown").notNull().default(0),
245
+ createdAt: text("created_at").notNull()
246
+ }, (table) => [
247
+ uniqueIndex("idx_bing_coverage_snap_project_date").on(table.projectId, table.date)
248
+ ]);
241
249
  var bingConnections = sqliteTable("bing_connections", {
242
250
  id: text("id").primaryKey(),
243
251
  domain: text("domain").notNull(),
@@ -283,8 +291,6 @@ var gaConnections = sqliteTable("ga_connections", {
283
291
  projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
284
292
  propertyId: text("property_id").notNull(),
285
293
  clientEmail: text("client_email").notNull(),
286
- // WARNING: Authentication material should be stored in config.yaml per CLAUDE.md
287
- privateKey: text("private_key").notNull(),
288
294
  createdAt: text("created_at").notNull(),
289
295
  updatedAt: text("updated_at").notNull()
290
296
  }, (table) => [
@@ -795,7 +801,22 @@ var MIGRATIONS = [
795
801
  )`,
796
802
  `CREATE INDEX IF NOT EXISTS idx_ga_social_ref_project_date ON ga_social_referrals(project_id, date)`,
797
803
  `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)`
804
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_social_ref_unique ON ga_social_referrals(project_id, date, source, medium, channel_group)`,
805
+ // v26: Bing coverage snapshots for historical tracking (mirrors gsc_coverage_snapshots)
806
+ `CREATE TABLE IF NOT EXISTS bing_coverage_snapshots (
807
+ id TEXT PRIMARY KEY,
808
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
809
+ date TEXT NOT NULL,
810
+ indexed INTEGER NOT NULL DEFAULT 0,
811
+ not_indexed INTEGER NOT NULL DEFAULT 0,
812
+ unknown INTEGER NOT NULL DEFAULT 0,
813
+ created_at TEXT NOT NULL
814
+ )`,
815
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_bing_coverage_snap_project_date ON bing_coverage_snapshots(project_id, date)`
816
+ // v27: Credential columns removed from Drizzle schema — credentials now live in config.yaml.
817
+ // Physical columns (access_token, refresh_token, token_expires_at on google_connections;
818
+ // private_key on ga_connections) intentionally retained in DB for one-time migration in server.ts.
819
+ // SQLite does not support DROP COLUMN; no SQL to execute.
799
820
  ];
800
821
  function isDuplicateColumnError(err) {
801
822
  if (!(err instanceof Error)) return false;
@@ -1239,6 +1260,7 @@ export {
1239
1260
  gscSearchData,
1240
1261
  gscUrlInspections,
1241
1262
  gscCoverageSnapshots,
1263
+ bingCoverageSnapshots,
1242
1264
  bingUrlInspections,
1243
1265
  gaTrafficSnapshots,
1244
1266
  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-6HJVQBQM.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-WBV2D7FB.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-QF6E4HBV.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-6HJVQBQM.js";
5
+ import "./chunk-WBV2D7FB.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-WBV2D7FB.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.1",
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,20 +54,20 @@
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
59
  "@ainyc/canonry-db": "0.0.0",
60
+ "@ainyc/canonry-contracts": "0.0.0",
60
61
  "@ainyc/canonry-intelligence": "0.0.0",
61
- "@ainyc/canonry-api-routes": "0.0.0",
62
- "@ainyc/canonry-integration-google": "0.0.0",
63
62
  "@ainyc/canonry-integration-bing": "0.0.0",
64
63
  "@ainyc/canonry-integration-wordpress": "0.0.0",
65
64
  "@ainyc/canonry-provider-cdp": "0.0.0",
65
+ "@ainyc/canonry-provider-local": "0.0.0",
66
+ "@ainyc/canonry-integration-google": "0.0.0",
66
67
  "@ainyc/canonry-provider-claude": "0.0.0",
67
- "@ainyc/canonry-provider-gemini": "0.0.0",
68
68
  "@ainyc/canonry-provider-openai": "0.0.0",
69
- "@ainyc/canonry-provider-local": "0.0.0",
70
- "@ainyc/canonry-provider-perplexity": "0.0.0"
69
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
70
+ "@ainyc/canonry-provider-gemini": "0.0.0"
71
71
  },
72
72
  "scripts": {
73
73
  "build": "tsup && tsx build-web.ts",