@ainyc/canonry 1.37.1 → 1.39.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/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node --import tsx
2
2
  import {
3
+ IntelligenceService,
3
4
  apiKeys,
4
5
  configExists,
5
6
  createClient,
@@ -26,22 +27,26 @@ import {
26
27
  setGoogleAuthConfig,
27
28
  showFirstRunNotice,
28
29
  trackEvent
29
- } from "./chunk-U2ZNKIWK.js";
30
+ } from "./chunk-4W7EW7SN.js";
30
31
 
31
32
  // src/cli.ts
32
33
  import { pathToFileURL } from "url";
33
34
 
34
35
  // src/cli-error.ts
36
+ var EXIT_USER_ERROR = 1;
37
+ var EXIT_SYSTEM_ERROR = 2;
35
38
  var CliError = class extends Error {
36
39
  code;
37
40
  displayMessage;
38
41
  details;
42
+ exitCode;
39
43
  constructor(options) {
40
44
  super(options.message);
41
45
  this.name = "CliError";
42
46
  this.code = options.code;
43
47
  this.displayMessage = options.displayMessage;
44
48
  this.details = options.details;
49
+ this.exitCode = options.exitCode ?? EXIT_USER_ERROR;
45
50
  }
46
51
  };
47
52
  function usageError(displayMessage, options) {
@@ -223,6 +228,41 @@ async function backfillAnswerVisibilityCommand(opts) {
223
228
  console.log(` Updated: ${updated}`);
224
229
  console.log(` Visible: ${visible}`);
225
230
  }
231
+ async function backfillInsightsCommand(project, opts) {
232
+ const config = loadConfig();
233
+ const db = createClient(config.database);
234
+ migrate(db);
235
+ const service = new IntelligenceService(db);
236
+ const isJson = opts?.format === "json";
237
+ if (!isJson) {
238
+ process.stderr.write(`Backfilling insights for "${project}"...
239
+ `);
240
+ }
241
+ const result = service.backfill(project, {
242
+ fromRunId: opts?.fromRun,
243
+ toRunId: opts?.toRun
244
+ }, (info) => {
245
+ if (!isJson) {
246
+ process.stderr.write(` [${info.index}/${info.total}] ${info.runId} \u2014 ${info.insights} insights
247
+ `);
248
+ }
249
+ });
250
+ const output = {
251
+ project,
252
+ processed: result.processed,
253
+ skipped: result.skipped,
254
+ totalInsights: result.totalInsights
255
+ };
256
+ if (isJson) {
257
+ console.log(JSON.stringify(output, null, 2));
258
+ return;
259
+ }
260
+ console.log(`
261
+ Backfill complete.`);
262
+ console.log(` Processed: ${result.processed}`);
263
+ console.log(` Skipped: ${result.skipped}`);
264
+ console.log(` Insights: ${result.totalInsights}`);
265
+ }
226
266
 
227
267
  // src/cli-command-helpers.ts
228
268
  function getString(values, key) {
@@ -320,14 +360,31 @@ var BACKFILL_CLI_COMMANDS = [
320
360
  });
321
361
  }
322
362
  },
363
+ {
364
+ path: ["backfill", "insights"],
365
+ usage: "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--format json]",
366
+ options: {
367
+ "from-run": stringOption(),
368
+ "to-run": stringOption()
369
+ },
370
+ run: async (input) => {
371
+ const usage = "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--format json]";
372
+ const project = requireProject(input, "backfill insights", usage);
373
+ await backfillInsightsCommand(project, {
374
+ fromRun: getString(input.values, "from-run"),
375
+ toRun: getString(input.values, "to-run"),
376
+ format: input.format
377
+ });
378
+ }
379
+ },
323
380
  {
324
381
  path: ["backfill"],
325
- usage: "canonry backfill <answer-visibility> [--project <name>] [--format json]",
382
+ usage: "canonry backfill <answer-visibility|insights> [options]",
326
383
  run: async (input) => {
327
384
  unknownSubcommand(input.positionals[0], {
328
385
  command: "backfill",
329
- usage: "canonry backfill <answer-visibility> [--project <name>] [--format json]",
330
- available: ["answer-visibility"]
386
+ usage: "canonry backfill <answer-visibility|insights> [options]",
387
+ available: ["answer-visibility", "insights"]
331
388
  });
332
389
  }
333
390
  }
@@ -398,13 +455,16 @@ var ApiClient = class {
398
455
  body: serializedBody
399
456
  });
400
457
  } catch (err) {
458
+ if (err instanceof CliError) throw err;
401
459
  const msg = err instanceof Error ? err.message : String(err);
402
460
  if (msg.includes("fetch failed") || msg.includes("ECONNREFUSED") || msg.includes("connect ECONNREFUSED")) {
403
- throw new Error(
404
- `Could not connect to canonry server at ${this.baseUrl.replace("/api/v1", "")}. Start it with "canonry serve" (or "canonry serve &" to run in background).`
405
- );
461
+ throw new CliError({
462
+ code: "CONNECTION_ERROR",
463
+ message: `Could not connect to canonry server at ${this.baseUrl.replace("/api/v1", "")}. Start it with "canonry serve" (or "canonry serve &" to run in background).`,
464
+ exitCode: EXIT_SYSTEM_ERROR
465
+ });
406
466
  }
407
- throw err;
467
+ throw new CliError({ code: "CONNECTION_ERROR", message: msg, exitCode: EXIT_SYSTEM_ERROR });
408
468
  }
409
469
  if (!res.ok) {
410
470
  let errorBody;
@@ -413,8 +473,11 @@ var ApiClient = class {
413
473
  } catch {
414
474
  errorBody = { error: { code: "UNKNOWN", message: res.statusText } };
415
475
  }
416
- const msg = errorBody && typeof errorBody === "object" && "error" in errorBody && errorBody.error && typeof errorBody.error === "object" && "message" in errorBody.error ? String(errorBody.error.message) : `HTTP ${res.status}: ${res.statusText}`;
417
- throw new Error(msg);
476
+ const errorObj = errorBody && typeof errorBody === "object" && "error" in errorBody && errorBody.error && typeof errorBody.error === "object" ? errorBody.error : null;
477
+ const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${res.status}: ${res.statusText}`;
478
+ const code = errorObj?.code ? String(errorObj.code) : "API_ERROR";
479
+ const exitCode = res.status >= 500 ? EXIT_SYSTEM_ERROR : EXIT_USER_ERROR;
480
+ throw new CliError({ code, message: msg, exitCode });
418
481
  }
419
482
  if (res.status === 204) {
420
483
  return void 0;
@@ -744,6 +807,24 @@ var ApiClient = class {
744
807
  async wordpressStagingPush(project) {
745
808
  return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/staging/push`);
746
809
  }
810
+ // ── Intelligence ──────────────────────────────────────────────────────
811
+ async getInsights(project, opts) {
812
+ const params = new URLSearchParams();
813
+ if (opts?.dismissed) params.set("dismissed", "true");
814
+ if (opts?.runId) params.set("runId", opts.runId);
815
+ const qs = params.toString();
816
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/insights${qs ? `?${qs}` : ""}`);
817
+ }
818
+ async dismissInsight(project, id) {
819
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/insights/${encodeURIComponent(id)}/dismiss`);
820
+ }
821
+ async getHealth(project) {
822
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/health/latest`);
823
+ }
824
+ async getHealthHistory(project, limit) {
825
+ const qs = limit ? `?limit=${limit}` : "";
826
+ return this.request("GET", `/projects/${encodeURIComponent(project)}/health/history${qs}`);
827
+ }
747
828
  };
748
829
 
749
830
  // src/commands/bing.ts
@@ -751,22 +832,12 @@ function getClient() {
751
832
  return createApiClient();
752
833
  }
753
834
  async function bingConnect(project, opts) {
754
- let apiKey = opts?.apiKey;
755
- if (!apiKey) {
756
- const readline2 = await import("readline");
757
- const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
758
- apiKey = await new Promise((resolve) => {
759
- rl.question("Bing Webmaster Tools API key: ", (answer) => {
760
- rl.close();
761
- resolve(answer.trim());
762
- });
763
- });
764
- }
835
+ const apiKey = opts?.apiKey;
765
836
  if (!apiKey) {
766
837
  throw new CliError({
767
838
  code: "BING_API_KEY_REQUIRED",
768
- message: "API key is required (pass --api-key or enter interactively)",
769
- displayMessage: "Error: API key is required (pass --api-key or enter interactively)",
839
+ message: "API key is required. Pass --api-key <key>.",
840
+ displayMessage: "Error: API key is required. Pass --api-key <key>.",
770
841
  details: {
771
842
  project
772
843
  }
@@ -3287,13 +3358,13 @@ async function showStatus(project, format) {
3287
3358
  console.log(JSON.stringify({ project: projectData, runs: runs2 }, null, 2));
3288
3359
  return;
3289
3360
  }
3290
- console.log(`Status: ${projectData.displayName} (${projectData.name})
3361
+ console.log(`Status: ${projectData.displayName ?? projectData.name} (${projectData.name})
3291
3362
  `);
3292
3363
  console.log(` Domain: ${projectData.canonicalDomain}`);
3293
3364
  console.log(` Country: ${projectData.country}`);
3294
3365
  console.log(` Language: ${projectData.language}`);
3295
3366
  if (runs2.length > 0) {
3296
- const latest = runs2[runs2.length - 1];
3367
+ const latest = runs2[0];
3297
3368
  console.log(`
3298
3369
  Latest run:`);
3299
3370
  console.log(` ID: ${latest.id}`);
@@ -3313,25 +3384,25 @@ async function showStatus(project, format) {
3313
3384
  var OPERATOR_CLI_COMMANDS = [
3314
3385
  {
3315
3386
  path: ["status"],
3316
- usage: "canonry status <project>",
3387
+ usage: "canonry status <project> [--format json]",
3317
3388
  run: async (input) => {
3318
- const project = requireProject(input, "status", "canonry status <project>");
3389
+ const project = requireProject(input, "status", "canonry status <project> [--format json]");
3319
3390
  await showStatus(project, input.format);
3320
3391
  }
3321
3392
  },
3322
3393
  {
3323
3394
  path: ["evidence"],
3324
- usage: "canonry evidence <project>",
3395
+ usage: "canonry evidence <project> [--format json]",
3325
3396
  run: async (input) => {
3326
- const project = requireProject(input, "evidence", "canonry evidence <project>");
3397
+ const project = requireProject(input, "evidence", "canonry evidence <project> [--format json]");
3327
3398
  await showEvidence(project, input.format);
3328
3399
  }
3329
3400
  },
3330
3401
  {
3331
3402
  path: ["history"],
3332
- usage: "canonry history <project>",
3403
+ usage: "canonry history <project> [--format json]",
3333
3404
  run: async (input) => {
3334
- const project = requireProject(input, "history", "canonry history <project>");
3405
+ const project = requireProject(input, "history", "canonry history <project> [--format json]");
3335
3406
  await showHistory(project, input.format);
3336
3407
  }
3337
3408
  },
@@ -3437,7 +3508,7 @@ async function showProject(name, format) {
3437
3508
  console.log(JSON.stringify(project, null, 2));
3438
3509
  return;
3439
3510
  }
3440
- console.log(`Project: ${project.displayName}
3511
+ console.log(`Project: ${project.displayName ?? project.name}
3441
3512
  `);
3442
3513
  console.log(` Name: ${project.name}`);
3443
3514
  console.log(` ID: ${project.id}`);
@@ -3453,8 +3524,8 @@ async function showProject(name, format) {
3453
3524
  console.log(` Tags: ${project.tags.length > 0 ? project.tags.join(", ") : "(none)"}`);
3454
3525
  const labelEntries = Object.entries(project.labels);
3455
3526
  console.log(` Labels: ${labelEntries.length > 0 ? labelEntries.map(([k, v]) => `${k}=${v}`).join(", ") : "(none)"}`);
3456
- console.log(` Created: ${project.createdAt}`);
3457
- console.log(` Updated: ${project.updatedAt}`);
3527
+ if (project.createdAt) console.log(` Created: ${project.createdAt}`);
3528
+ if (project.updatedAt) console.log(` Updated: ${project.updatedAt}`);
3458
3529
  }
3459
3530
  async function updateProjectSettings(name, opts) {
3460
3531
  const client = getClient12();
@@ -3469,7 +3540,7 @@ async function updateProjectSettings(name, opts) {
3469
3540
  ownedDomains = ownedDomains.filter((d) => !toRemove.has(d));
3470
3541
  }
3471
3542
  const result = await client.putProject(name, {
3472
- displayName: opts.displayName ?? project.displayName,
3543
+ displayName: opts.displayName ?? project.displayName ?? project.name,
3473
3544
  canonicalDomain: opts.domain ?? project.canonicalDomain,
3474
3545
  ownedDomains,
3475
3546
  country: opts.country ?? project.country,
@@ -4855,6 +4926,126 @@ var SNAPSHOT_CLI_COMMANDS = [
4855
4926
  }
4856
4927
  ];
4857
4928
 
4929
+ // src/commands/insights.ts
4930
+ async function listInsights(project, opts) {
4931
+ const client = createApiClient();
4932
+ const insights = await client.getInsights(project, { dismissed: opts.dismissed });
4933
+ if (opts.format === "json") {
4934
+ console.log(JSON.stringify(insights, null, 2));
4935
+ return;
4936
+ }
4937
+ if (insights.length === 0) {
4938
+ console.log("No insights found.");
4939
+ return;
4940
+ }
4941
+ for (const insight of insights) {
4942
+ const severity = insight.severity.toUpperCase().padEnd(8);
4943
+ const dismissed = insight.dismissed ? " [dismissed]" : "";
4944
+ console.log(`${severity} ${insight.type.padEnd(12)} ${insight.title}${dismissed}`);
4945
+ if (insight.recommendation) {
4946
+ console.log(` Action: ${insight.recommendation.action}${insight.recommendation.target ? ` \u2192 ${insight.recommendation.target}` : ""}`);
4947
+ console.log(` Reason: ${insight.recommendation.reason}`);
4948
+ }
4949
+ if (insight.cause) {
4950
+ console.log(` Cause: ${insight.cause.cause}${insight.cause.competitorDomain ? ` (${insight.cause.competitorDomain})` : ""}`);
4951
+ }
4952
+ console.log("");
4953
+ }
4954
+ }
4955
+ async function dismissInsight(project, id, opts) {
4956
+ const client = createApiClient();
4957
+ const result = await client.dismissInsight(project, id);
4958
+ if (opts.format === "json") {
4959
+ console.log(JSON.stringify(result, null, 2));
4960
+ return;
4961
+ }
4962
+ console.log(`Insight ${id} dismissed.`);
4963
+ }
4964
+
4965
+ // src/commands/health-cmd.ts
4966
+ async function showHealth(project, opts) {
4967
+ const client = createApiClient();
4968
+ if (opts.history) {
4969
+ const snapshots = await client.getHealthHistory(project, opts.limit);
4970
+ if (opts.format === "json") {
4971
+ console.log(JSON.stringify(snapshots, null, 2));
4972
+ return;
4973
+ }
4974
+ if (snapshots.length === 0) {
4975
+ console.log("No health history available.");
4976
+ return;
4977
+ }
4978
+ console.log("Date Cited Rate Cited/Total");
4979
+ console.log("\u2500".repeat(55));
4980
+ for (const snap of snapshots) {
4981
+ const rate2 = (snap.overallCitedRate * 100).toFixed(1).padStart(5) + "%";
4982
+ const ratio = `${snap.citedPairs}/${snap.totalPairs}`;
4983
+ const date = snap.createdAt.slice(0, 19).padEnd(25);
4984
+ console.log(`${date} ${rate2} ${ratio}`);
4985
+ }
4986
+ return;
4987
+ }
4988
+ const health = await client.getHealth(project);
4989
+ if (opts.format === "json") {
4990
+ console.log(JSON.stringify(health, null, 2));
4991
+ return;
4992
+ }
4993
+ const rate = (health.overallCitedRate * 100).toFixed(1);
4994
+ console.log(`Health: ${rate}% cited (${health.citedPairs}/${health.totalPairs} pairs)`);
4995
+ console.log("");
4996
+ if (health.providerBreakdown && Object.keys(health.providerBreakdown).length > 0) {
4997
+ console.log("Provider Breakdown:");
4998
+ for (const [provider, stats] of Object.entries(health.providerBreakdown)) {
4999
+ const pRate = (stats.citedRate * 100).toFixed(1);
5000
+ console.log(` ${provider.padEnd(15)} ${pRate}% (${stats.cited}/${stats.total})`);
5001
+ }
5002
+ }
5003
+ }
5004
+
5005
+ // src/cli-commands/intelligence.ts
5006
+ var INTELLIGENCE_CLI_COMMANDS = [
5007
+ {
5008
+ path: ["insights"],
5009
+ usage: "canonry insights <project> [--dismissed] [--format json]",
5010
+ options: {
5011
+ dismissed: { type: "boolean" }
5012
+ },
5013
+ run: async (input) => {
5014
+ const usage = "canonry insights <project> [--dismissed] [--format json]";
5015
+ const project = requireProject(input, "insights", usage);
5016
+ const dismissed = input.values.dismissed === true;
5017
+ await listInsights(project, { dismissed, format: input.format });
5018
+ }
5019
+ },
5020
+ {
5021
+ path: ["insights", "dismiss"],
5022
+ usage: "canonry insights dismiss <project> <id> [--format json]",
5023
+ options: {},
5024
+ run: async (input) => {
5025
+ const usage = "canonry insights dismiss <project> <id> [--format json]";
5026
+ const project = requireProject(input, "insights dismiss", usage);
5027
+ const id = requirePositional(input, 1, { command: "insights dismiss", usage, message: "insight ID is required" });
5028
+ await dismissInsight(project, id, { format: input.format });
5029
+ }
5030
+ },
5031
+ {
5032
+ path: ["health"],
5033
+ usage: "canonry health <project> [--history] [--limit <n>] [--format json]",
5034
+ options: {
5035
+ history: { type: "boolean" },
5036
+ limit: { type: "string" }
5037
+ },
5038
+ run: async (input) => {
5039
+ const usage = "canonry health <project> [--history] [--limit <n>] [--format json]";
5040
+ const project = requireProject(input, "health", usage);
5041
+ const history = input.values.history === true;
5042
+ const limitStr = getString(input.values, "limit");
5043
+ const limit = limitStr ? Number.parseInt(limitStr, 10) : void 0;
5044
+ await showHealth(project, { history, limit, format: input.format });
5045
+ }
5046
+ }
5047
+ ];
5048
+
4858
5049
  // src/commands/bootstrap.ts
4859
5050
  import crypto from "crypto";
4860
5051
  import path2 from "path";
@@ -5035,15 +5226,17 @@ async function bootstrapCommand(_opts) {
5035
5226
  const keyPrefix = rawApiKey.slice(0, 9);
5036
5227
  const db = createClient(databasePath);
5037
5228
  migrate(db);
5038
- db.delete(apiKeys).where(eq2(apiKeys.name, "default")).run();
5039
- db.insert(apiKeys).values({
5040
- id: crypto.randomUUID(),
5041
- name: "default",
5042
- keyHash,
5043
- keyPrefix,
5044
- scopes: '["*"]',
5045
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
5046
- }).run();
5229
+ db.transaction((tx) => {
5230
+ tx.delete(apiKeys).where(eq2(apiKeys.name, "default")).run();
5231
+ tx.insert(apiKeys).values({
5232
+ id: crypto.randomUUID(),
5233
+ name: "default",
5234
+ keyHash,
5235
+ keyPrefix,
5236
+ scopes: '["*"]',
5237
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5238
+ }).run();
5239
+ });
5047
5240
  saveConfig({
5048
5241
  apiUrl: env.apiUrl || existingConfig?.apiUrl || `http://localhost:${process.env.CANONRY_PORT || "4100"}`,
5049
5242
  database: databasePath,
@@ -5454,6 +5647,15 @@ async function serveCommand(format = "text") {
5454
5647
  const db = createClient(config.database);
5455
5648
  migrate(db);
5456
5649
  const app = await createServer({ config, db });
5650
+ const shutdown = () => {
5651
+ app.close().then(() => {
5652
+ process.exit(0);
5653
+ }).catch(() => {
5654
+ process.exit(1);
5655
+ });
5656
+ };
5657
+ process.on("SIGTERM", shutdown);
5658
+ process.on("SIGINT", shutdown);
5457
5659
  try {
5458
5660
  await app.listen({ host, port });
5459
5661
  const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
@@ -5758,16 +5960,6 @@ import fs6 from "fs";
5758
5960
  function getClient17() {
5759
5961
  return createApiClient();
5760
5962
  }
5761
- async function promptForAppPassword() {
5762
- const readline2 = await import("readline");
5763
- const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
5764
- return new Promise((resolve) => {
5765
- rl.question("WordPress Application Password: ", (answer) => {
5766
- rl.close();
5767
- resolve(answer.trim());
5768
- });
5769
- });
5770
- }
5771
5963
  function printJson(value) {
5772
5964
  console.log(JSON.stringify(value, null, 2));
5773
5965
  }
@@ -5903,12 +6095,12 @@ Staging:`);
5903
6095
  console.log(` Snippet: ${diff.staging.contentSnippet || "(empty)"}`);
5904
6096
  }
5905
6097
  async function wordpressConnect(project, opts) {
5906
- const appPassword = opts.appPassword ?? await promptForAppPassword();
6098
+ const appPassword = opts.appPassword;
5907
6099
  if (!appPassword) {
5908
6100
  throw new CliError({
5909
6101
  code: "WORDPRESS_APP_PASSWORD_REQUIRED",
5910
6102
  message: "WordPress Application Password is required",
5911
- displayMessage: "Error: WordPress Application Password is required (pass --app-password or enter interactively).",
6103
+ displayMessage: "Error: WordPress Application Password is required. Pass --app-password <password>.",
5912
6104
  details: { project }
5913
6105
  });
5914
6106
  }
@@ -6200,12 +6392,12 @@ async function wordpressSchemaStatus(project, opts) {
6200
6392
  }
6201
6393
  }
6202
6394
  async function wordpressOnboard(project, opts) {
6203
- const appPassword = opts.appPassword ?? await promptForAppPassword();
6395
+ const appPassword = opts.appPassword;
6204
6396
  if (!appPassword) {
6205
6397
  throw new CliError({
6206
6398
  code: "WORDPRESS_APP_PASSWORD_REQUIRED",
6207
6399
  message: "WordPress Application Password is required",
6208
- displayMessage: "Error: WordPress Application Password is required (pass --app-password or enter interactively).",
6400
+ displayMessage: "Error: WordPress Application Password is required. Pass --app-password <password>.",
6209
6401
  details: { project }
6210
6402
  });
6211
6403
  }
@@ -6815,7 +7007,8 @@ var REGISTERED_CLI_COMMANDS = [
6815
7007
  ...BING_CLI_COMMANDS,
6816
7008
  ...WORDPRESS_CLI_COMMANDS,
6817
7009
  ...CDP_CLI_COMMANDS,
6818
- ...GA_CLI_COMMANDS
7010
+ ...GA_CLI_COMMANDS,
7011
+ ...INTELLIGENCE_CLI_COMMANDS
6819
7012
  ];
6820
7013
 
6821
7014
  // src/cli.ts
@@ -6828,6 +7021,7 @@ Usage:
6828
7021
  canonry init --gemini-key <key> Initialize non-interactively (also reads env vars)
6829
7022
  canonry bootstrap [--force] Bootstrap config/database from env vars
6830
7023
  canonry backfill answer-visibility Backfill answer-level visibility from stored answers
7024
+ canonry backfill insights <project> Backfill intelligence insights [--from-run <id>] [--to-run <id>]
6831
7025
  canonry serve Start the local server (foreground)
6832
7026
  canonry start Start the server as a background daemon
6833
7027
  canonry stop Stop the background daemon
@@ -6857,10 +7051,13 @@ Usage:
6857
7051
  canonry run --all Trigger runs for all projects
6858
7052
  canonry run show <id> Show run details and snapshots
6859
7053
  canonry runs <project> List runs for a project (--limit <n>)
6860
- canonry status <project> Show project summary
6861
- canonry evidence <project> Show per-phrase results
7054
+ canonry status <project> Show project summary [--format json]
7055
+ canonry evidence <project> Show per-phrase results [--format json]
6862
7056
  canonry analytics <project> Show analytics (--feature metrics|gaps|sources, --window 7d|30d|90d|all)
6863
- canonry history <project> Show audit trail
7057
+ canonry insights <project> Show intelligence insights [--dismissed] [--format json]
7058
+ canonry insights dismiss <project> <id> Dismiss an insight [--format json]
7059
+ canonry health <project> Show citation health [--history] [--limit <n>] [--format json]
7060
+ canonry history <project> Show audit trail [--format json]
6864
7061
  canonry export <project> Export project as YAML
6865
7062
  canonry apply <file...> Apply declarative config (multi-doc YAML supported)
6866
7063
  canonry schedule set <project> Set schedule (--preset or --cron)
@@ -7019,7 +7216,7 @@ Run "canonry --help" for usage.`, {
7019
7216
  });
7020
7217
  } catch (err) {
7021
7218
  printCliError(err, format);
7022
- return 1;
7219
+ return err instanceof CliError ? err.exitCode : EXIT_SYSTEM_ERROR;
7023
7220
  }
7024
7221
  }
7025
7222
  async function main(args = process.argv.slice(2)) {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-U2ZNKIWK.js";
4
+ } from "./chunk-4W7EW7SN.js";
5
5
  export {
6
6
  createServer,
7
7
  loadConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "1.37.1",
3
+ "version": "1.39.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",
@@ -55,17 +55,18 @@
55
55
  "tsup": "^8.5.1",
56
56
  "tsx": "^4.19.0",
57
57
  "@ainyc/canonry-api-routes": "0.0.0",
58
- "@ainyc/canonry-contracts": "0.0.0",
59
- "@ainyc/canonry-integration-google": "0.0.0",
60
- "@ainyc/canonry-db": "0.0.0",
61
58
  "@ainyc/canonry-config": "0.0.0",
59
+ "@ainyc/canonry-db": "0.0.0",
60
+ "@ainyc/canonry-contracts": "0.0.0",
62
61
  "@ainyc/canonry-integration-bing": "0.0.0",
62
+ "@ainyc/canonry-intelligence": "0.0.0",
63
+ "@ainyc/canonry-integration-google": "0.0.0",
63
64
  "@ainyc/canonry-integration-wordpress": "0.0.0",
64
65
  "@ainyc/canonry-provider-cdp": "0.0.0",
65
- "@ainyc/canonry-provider-local": "0.0.0",
66
- "@ainyc/canonry-provider-openai": "0.0.0",
67
66
  "@ainyc/canonry-provider-claude": "0.0.0",
68
67
  "@ainyc/canonry-provider-gemini": "0.0.0",
68
+ "@ainyc/canonry-provider-local": "0.0.0",
69
+ "@ainyc/canonry-provider-openai": "0.0.0",
69
70
  "@ainyc/canonry-provider-perplexity": "0.0.0"
70
71
  },
71
72
  "scripts": {