@ainyc/canonry 1.37.0 → 1.38.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
@@ -26,22 +26,26 @@ import {
26
26
  setGoogleAuthConfig,
27
27
  showFirstRunNotice,
28
28
  trackEvent
29
- } from "./chunk-5MOWJJND.js";
29
+ } from "./chunk-SAVCFB5B.js";
30
30
 
31
31
  // src/cli.ts
32
32
  import { pathToFileURL } from "url";
33
33
 
34
34
  // src/cli-error.ts
35
+ var EXIT_USER_ERROR = 1;
36
+ var EXIT_SYSTEM_ERROR = 2;
35
37
  var CliError = class extends Error {
36
38
  code;
37
39
  displayMessage;
38
40
  details;
41
+ exitCode;
39
42
  constructor(options) {
40
43
  super(options.message);
41
44
  this.name = "CliError";
42
45
  this.code = options.code;
43
46
  this.displayMessage = options.displayMessage;
44
47
  this.details = options.details;
48
+ this.exitCode = options.exitCode ?? EXIT_USER_ERROR;
45
49
  }
46
50
  };
47
51
  function usageError(displayMessage, options) {
@@ -398,13 +402,16 @@ var ApiClient = class {
398
402
  body: serializedBody
399
403
  });
400
404
  } catch (err) {
405
+ if (err instanceof CliError) throw err;
401
406
  const msg = err instanceof Error ? err.message : String(err);
402
407
  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
- );
408
+ throw new CliError({
409
+ code: "CONNECTION_ERROR",
410
+ 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).`,
411
+ exitCode: EXIT_SYSTEM_ERROR
412
+ });
406
413
  }
407
- throw err;
414
+ throw new CliError({ code: "CONNECTION_ERROR", message: msg, exitCode: EXIT_SYSTEM_ERROR });
408
415
  }
409
416
  if (!res.ok) {
410
417
  let errorBody;
@@ -413,8 +420,11 @@ var ApiClient = class {
413
420
  } catch {
414
421
  errorBody = { error: { code: "UNKNOWN", message: res.statusText } };
415
422
  }
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);
423
+ const errorObj = errorBody && typeof errorBody === "object" && "error" in errorBody && errorBody.error && typeof errorBody.error === "object" ? errorBody.error : null;
424
+ const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${res.status}: ${res.statusText}`;
425
+ const code = errorObj?.code ? String(errorObj.code) : "API_ERROR";
426
+ const exitCode = res.status >= 500 ? EXIT_SYSTEM_ERROR : EXIT_USER_ERROR;
427
+ throw new CliError({ code, message: msg, exitCode });
418
428
  }
419
429
  if (res.status === 204) {
420
430
  return void 0;
@@ -751,22 +761,12 @@ function getClient() {
751
761
  return createApiClient();
752
762
  }
753
763
  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
- }
764
+ const apiKey = opts?.apiKey;
765
765
  if (!apiKey) {
766
766
  throw new CliError({
767
767
  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)",
768
+ message: "API key is required. Pass --api-key <key>.",
769
+ displayMessage: "Error: API key is required. Pass --api-key <key>.",
770
770
  details: {
771
771
  project
772
772
  }
@@ -1698,7 +1698,11 @@ async function addCompetitors(project, domains, format) {
1698
1698
  }, null, 2));
1699
1699
  return;
1700
1700
  }
1701
- console.log(`Added ${domains.length} competitor(s) to "${project}".`);
1701
+ if (addedDomains.length === 0) {
1702
+ console.log(`No new competitors added to "${project}" (all already tracked).`);
1703
+ } else {
1704
+ console.log(`Added ${addedDomains.length} competitor(s) to "${project}".`);
1705
+ }
1702
1706
  }
1703
1707
  async function listCompetitors(project, format) {
1704
1708
  const client = getClient4();
@@ -3283,13 +3287,13 @@ async function showStatus(project, format) {
3283
3287
  console.log(JSON.stringify({ project: projectData, runs: runs2 }, null, 2));
3284
3288
  return;
3285
3289
  }
3286
- console.log(`Status: ${projectData.displayName} (${projectData.name})
3290
+ console.log(`Status: ${projectData.displayName ?? projectData.name} (${projectData.name})
3287
3291
  `);
3288
3292
  console.log(` Domain: ${projectData.canonicalDomain}`);
3289
3293
  console.log(` Country: ${projectData.country}`);
3290
3294
  console.log(` Language: ${projectData.language}`);
3291
3295
  if (runs2.length > 0) {
3292
- const latest = runs2[runs2.length - 1];
3296
+ const latest = runs2[0];
3293
3297
  console.log(`
3294
3298
  Latest run:`);
3295
3299
  console.log(` ID: ${latest.id}`);
@@ -3309,25 +3313,25 @@ async function showStatus(project, format) {
3309
3313
  var OPERATOR_CLI_COMMANDS = [
3310
3314
  {
3311
3315
  path: ["status"],
3312
- usage: "canonry status <project>",
3316
+ usage: "canonry status <project> [--format json]",
3313
3317
  run: async (input) => {
3314
- const project = requireProject(input, "status", "canonry status <project>");
3318
+ const project = requireProject(input, "status", "canonry status <project> [--format json]");
3315
3319
  await showStatus(project, input.format);
3316
3320
  }
3317
3321
  },
3318
3322
  {
3319
3323
  path: ["evidence"],
3320
- usage: "canonry evidence <project>",
3324
+ usage: "canonry evidence <project> [--format json]",
3321
3325
  run: async (input) => {
3322
- const project = requireProject(input, "evidence", "canonry evidence <project>");
3326
+ const project = requireProject(input, "evidence", "canonry evidence <project> [--format json]");
3323
3327
  await showEvidence(project, input.format);
3324
3328
  }
3325
3329
  },
3326
3330
  {
3327
3331
  path: ["history"],
3328
- usage: "canonry history <project>",
3332
+ usage: "canonry history <project> [--format json]",
3329
3333
  run: async (input) => {
3330
- const project = requireProject(input, "history", "canonry history <project>");
3334
+ const project = requireProject(input, "history", "canonry history <project> [--format json]");
3331
3335
  await showHistory(project, input.format);
3332
3336
  }
3333
3337
  },
@@ -3433,7 +3437,7 @@ async function showProject(name, format) {
3433
3437
  console.log(JSON.stringify(project, null, 2));
3434
3438
  return;
3435
3439
  }
3436
- console.log(`Project: ${project.displayName}
3440
+ console.log(`Project: ${project.displayName ?? project.name}
3437
3441
  `);
3438
3442
  console.log(` Name: ${project.name}`);
3439
3443
  console.log(` ID: ${project.id}`);
@@ -3449,8 +3453,8 @@ async function showProject(name, format) {
3449
3453
  console.log(` Tags: ${project.tags.length > 0 ? project.tags.join(", ") : "(none)"}`);
3450
3454
  const labelEntries = Object.entries(project.labels);
3451
3455
  console.log(` Labels: ${labelEntries.length > 0 ? labelEntries.map(([k, v]) => `${k}=${v}`).join(", ") : "(none)"}`);
3452
- console.log(` Created: ${project.createdAt}`);
3453
- console.log(` Updated: ${project.updatedAt}`);
3456
+ if (project.createdAt) console.log(` Created: ${project.createdAt}`);
3457
+ if (project.updatedAt) console.log(` Updated: ${project.updatedAt}`);
3454
3458
  }
3455
3459
  async function updateProjectSettings(name, opts) {
3456
3460
  const client = getClient12();
@@ -3465,7 +3469,7 @@ async function updateProjectSettings(name, opts) {
3465
3469
  ownedDomains = ownedDomains.filter((d) => !toRemove.has(d));
3466
3470
  }
3467
3471
  const result = await client.putProject(name, {
3468
- displayName: opts.displayName ?? project.displayName,
3472
+ displayName: opts.displayName ?? project.displayName ?? project.name,
3469
3473
  canonicalDomain: opts.domain ?? project.canonicalDomain,
3470
3474
  ownedDomains,
3471
3475
  country: opts.country ?? project.country,
@@ -5450,6 +5454,15 @@ async function serveCommand(format = "text") {
5450
5454
  const db = createClient(config.database);
5451
5455
  migrate(db);
5452
5456
  const app = await createServer({ config, db });
5457
+ const shutdown = () => {
5458
+ app.close().then(() => {
5459
+ process.exit(0);
5460
+ }).catch(() => {
5461
+ process.exit(1);
5462
+ });
5463
+ };
5464
+ process.on("SIGTERM", shutdown);
5465
+ process.on("SIGINT", shutdown);
5453
5466
  try {
5454
5467
  await app.listen({ host, port });
5455
5468
  const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
@@ -5754,16 +5767,6 @@ import fs6 from "fs";
5754
5767
  function getClient17() {
5755
5768
  return createApiClient();
5756
5769
  }
5757
- async function promptForAppPassword() {
5758
- const readline2 = await import("readline");
5759
- const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
5760
- return new Promise((resolve) => {
5761
- rl.question("WordPress Application Password: ", (answer) => {
5762
- rl.close();
5763
- resolve(answer.trim());
5764
- });
5765
- });
5766
- }
5767
5770
  function printJson(value) {
5768
5771
  console.log(JSON.stringify(value, null, 2));
5769
5772
  }
@@ -5899,12 +5902,12 @@ Staging:`);
5899
5902
  console.log(` Snippet: ${diff.staging.contentSnippet || "(empty)"}`);
5900
5903
  }
5901
5904
  async function wordpressConnect(project, opts) {
5902
- const appPassword = opts.appPassword ?? await promptForAppPassword();
5905
+ const appPassword = opts.appPassword;
5903
5906
  if (!appPassword) {
5904
5907
  throw new CliError({
5905
5908
  code: "WORDPRESS_APP_PASSWORD_REQUIRED",
5906
5909
  message: "WordPress Application Password is required",
5907
- displayMessage: "Error: WordPress Application Password is required (pass --app-password or enter interactively).",
5910
+ displayMessage: "Error: WordPress Application Password is required. Pass --app-password <password>.",
5908
5911
  details: { project }
5909
5912
  });
5910
5913
  }
@@ -6196,12 +6199,12 @@ async function wordpressSchemaStatus(project, opts) {
6196
6199
  }
6197
6200
  }
6198
6201
  async function wordpressOnboard(project, opts) {
6199
- const appPassword = opts.appPassword ?? await promptForAppPassword();
6202
+ const appPassword = opts.appPassword;
6200
6203
  if (!appPassword) {
6201
6204
  throw new CliError({
6202
6205
  code: "WORDPRESS_APP_PASSWORD_REQUIRED",
6203
6206
  message: "WordPress Application Password is required",
6204
- displayMessage: "Error: WordPress Application Password is required (pass --app-password or enter interactively).",
6207
+ displayMessage: "Error: WordPress Application Password is required. Pass --app-password <password>.",
6205
6208
  details: { project }
6206
6209
  });
6207
6210
  }
@@ -6853,10 +6856,10 @@ Usage:
6853
6856
  canonry run --all Trigger runs for all projects
6854
6857
  canonry run show <id> Show run details and snapshots
6855
6858
  canonry runs <project> List runs for a project (--limit <n>)
6856
- canonry status <project> Show project summary
6857
- canonry evidence <project> Show per-phrase results
6859
+ canonry status <project> Show project summary [--format json]
6860
+ canonry evidence <project> Show per-phrase results [--format json]
6858
6861
  canonry analytics <project> Show analytics (--feature metrics|gaps|sources, --window 7d|30d|90d|all)
6859
- canonry history <project> Show audit trail
6862
+ canonry history <project> Show audit trail [--format json]
6860
6863
  canonry export <project> Export project as YAML
6861
6864
  canonry apply <file...> Apply declarative config (multi-doc YAML supported)
6862
6865
  canonry schedule set <project> Set schedule (--preset or --cron)
@@ -7015,7 +7018,7 @@ Run "canonry --help" for usage.`, {
7015
7018
  });
7016
7019
  } catch (err) {
7017
7020
  printCliError(err, format);
7018
- return 1;
7021
+ return err instanceof CliError ? err.exitCode : EXIT_SYSTEM_ERROR;
7019
7022
  }
7020
7023
  }
7021
7024
  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-5MOWJJND.js";
4
+ } from "./chunk-SAVCFB5B.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.0",
3
+ "version": "1.38.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,18 +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-config": "0.0.0",
59
- "@ainyc/canonry-contracts": "0.0.0",
60
- "@ainyc/canonry-db": "0.0.0",
61
- "@ainyc/canonry-integration-bing": "0.0.0",
62
58
  "@ainyc/canonry-integration-google": "0.0.0",
59
+ "@ainyc/canonry-db": "0.0.0",
63
60
  "@ainyc/canonry-integration-wordpress": "0.0.0",
64
- "@ainyc/canonry-provider-cdp": "0.0.0",
61
+ "@ainyc/canonry-integration-bing": "0.0.0",
62
+ "@ainyc/canonry-contracts": "0.0.0",
63
+ "@ainyc/canonry-config": "0.0.0",
65
64
  "@ainyc/canonry-provider-claude": "0.0.0",
65
+ "@ainyc/canonry-provider-cdp": "0.0.0",
66
66
  "@ainyc/canonry-provider-gemini": "0.0.0",
67
67
  "@ainyc/canonry-provider-openai": "0.0.0",
68
- "@ainyc/canonry-provider-local": "0.0.0",
69
- "@ainyc/canonry-provider-perplexity": "0.0.0"
68
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
69
+ "@ainyc/canonry-provider-local": "0.0.0"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "tsup && tsx build-web.ts",