@ainyc/canonry 2.8.2 → 2.10.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-U1lA1GKP.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-PhLDQh1e.js"></script>
16
16
  <link rel="stylesheet" crossorigin href="./assets/index-CAewPdsZ.css">
17
17
  </head>
18
18
  <body>
@@ -376,6 +376,18 @@ var runTriggerRequestSchema = z2.object({
376
376
  (data) => Number(Boolean(data.location)) + Number(Boolean(data.allLocations)) + Number(Boolean(data.noLocation)) <= 1,
377
377
  { message: 'Only one of "location", "allLocations", or "noLocation" may be provided' }
378
378
  );
379
+ var runProviderErrorSchema = z2.object({
380
+ /** Human-readable error message (best-effort extracted from `raw.error.message` / `raw.message`, otherwise the raw text with any `[provider-X]` prefix stripped). */
381
+ message: z2.string(),
382
+ /** Original provider response payload, if the underlying error body parsed as JSON. Use this for structured fields like HTTP status, error code, etc. */
383
+ raw: z2.unknown().optional()
384
+ });
385
+ var runErrorSchema = z2.object({
386
+ /** Top-level message for runs that failed without a per-provider error (e.g. user cancellation, internal scheduling failures). */
387
+ message: z2.string().optional(),
388
+ /** Per-provider errors for visibility-sweep runs that had at least one provider fail. */
389
+ providers: z2.record(z2.string(), runProviderErrorSchema).optional()
390
+ });
379
391
  var runDtoSchema = z2.object({
380
392
  id: z2.string(),
381
393
  projectId: z2.string(),
@@ -385,9 +397,70 @@ var runDtoSchema = z2.object({
385
397
  location: z2.string().nullable().optional(),
386
398
  startedAt: z2.string().nullable().optional(),
387
399
  finishedAt: z2.string().nullable().optional(),
388
- error: z2.string().nullable().optional(),
400
+ error: runErrorSchema.nullable().optional(),
389
401
  createdAt: z2.string()
390
402
  });
403
+ var PROVIDER_PREFIX = /^\[provider-[a-zA-Z0-9_-]+\]\s+/;
404
+ function parseProviderErrorMessage(msg) {
405
+ const stripped = msg.replace(PROVIDER_PREFIX, "");
406
+ try {
407
+ const raw = JSON.parse(stripped);
408
+ if (raw && typeof raw === "object") {
409
+ const inner = raw;
410
+ const fromErrorMessage = typeof inner.error?.message === "string" ? inner.error.message : void 0;
411
+ const fromMessage = typeof inner.message === "string" ? inner.message : void 0;
412
+ return { message: fromErrorMessage ?? fromMessage ?? stripped, raw };
413
+ }
414
+ } catch {
415
+ }
416
+ return { message: stripped };
417
+ }
418
+ function parseRunError(raw) {
419
+ if (!raw) return null;
420
+ let parsed;
421
+ try {
422
+ parsed = JSON.parse(raw);
423
+ } catch {
424
+ return { message: raw };
425
+ }
426
+ if (!parsed || typeof parsed !== "object") {
427
+ return { message: raw };
428
+ }
429
+ const obj = parsed;
430
+ const hasProviders = obj.providers && typeof obj.providers === "object";
431
+ const hasMessage = typeof obj.message === "string";
432
+ if (hasProviders || hasMessage) {
433
+ return parsed;
434
+ }
435
+ const providers = {};
436
+ for (const [name, val] of Object.entries(obj)) {
437
+ providers[name] = parseProviderErrorMessage(typeof val === "string" ? val : JSON.stringify(val));
438
+ }
439
+ return { providers };
440
+ }
441
+ function buildRunErrorFromMessages(messages) {
442
+ const providers = {};
443
+ for (const [name, msg] of messages) {
444
+ providers[name] = parseProviderErrorMessage(msg);
445
+ }
446
+ return { providers };
447
+ }
448
+ function serializeRunError(err) {
449
+ return JSON.stringify(err);
450
+ }
451
+ function formatRunErrorOneLine(err) {
452
+ if (err.providers) {
453
+ const entries = Object.entries(err.providers);
454
+ if (entries.length === 1) {
455
+ const [provider, detail] = entries[0];
456
+ return `${provider}: ${detail.message}`;
457
+ }
458
+ if (entries.length > 1) {
459
+ return entries.map(([p, d]) => `${p}: ${d.message}`).join(" \u2022 ");
460
+ }
461
+ }
462
+ return err.message ?? "Run failed.";
463
+ }
391
464
  var groundingSourceSchema = z2.object({
392
465
  uri: z2.string(),
393
466
  title: z2.string()
@@ -2131,6 +2204,10 @@ export {
2131
2204
  RunKinds,
2132
2205
  RunTriggers,
2133
2206
  runTriggerRequestSchema,
2207
+ parseRunError,
2208
+ buildRunErrorFromMessages,
2209
+ serializeRunError,
2210
+ formatRunErrorOneLine,
2134
2211
  snapshotRequestSchema,
2135
2212
  scheduleUpsertRequestSchema,
2136
2213
  parseWindow,
@@ -16,6 +16,7 @@ import {
16
16
  authInvalid,
17
17
  authRequired,
18
18
  brandKeyFromText,
19
+ buildRunErrorFromMessages,
19
20
  categorizeSource,
20
21
  categoryLabel,
21
22
  competitorBatchRequestSchema,
@@ -36,6 +37,7 @@ import {
36
37
  normalizeProjectDomain,
37
38
  notFound,
38
39
  notImplemented,
40
+ parseRunError,
39
41
  parseWindow,
40
42
  projectConfigSchema,
41
43
  projectUpsertRequestSchema,
@@ -45,13 +47,14 @@ import {
45
47
  runTriggerRequestSchema,
46
48
  saveConfigPatch,
47
49
  scheduleUpsertRequestSchema,
50
+ serializeRunError,
48
51
  snapshotRequestSchema,
49
52
  unsupportedKind,
50
53
  validationError,
51
54
  visibilityStateFromAnswerMentioned,
52
55
  windowCutoff,
53
56
  wordpressEnvSchema
54
- } from "./chunk-FPZUQADO.js";
57
+ } from "./chunk-KWQCQMPY.js";
55
58
  import {
56
59
  IntelligenceService,
57
60
  agentMemory,
@@ -1043,7 +1046,7 @@ async function runRoutes(app, opts) {
1043
1046
  const terminalStatuses = /* @__PURE__ */ new Set(["completed", "partial", "failed", "cancelled"]);
1044
1047
  if (terminalStatuses.has(run.status)) throw runNotCancellable(run.id, run.status);
1045
1048
  const now = (/* @__PURE__ */ new Date()).toISOString();
1046
- app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: "Cancelled by user" }).where(eq7(runs.id, run.id)).run();
1049
+ app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: serializeRunError({ message: "Cancelled by user" }) }).where(eq7(runs.id, run.id)).run();
1047
1050
  writeAuditLog(app.db, {
1048
1051
  projectId: run.projectId,
1049
1052
  actor: "api",
@@ -1080,7 +1083,7 @@ function formatRun(row) {
1080
1083
  location: row.location,
1081
1084
  startedAt: row.startedAt,
1082
1085
  finishedAt: row.finishedAt,
1083
- error: row.error,
1086
+ error: parseRunError(row.error),
1084
1087
  createdAt: row.createdAt
1085
1088
  };
1086
1089
  }
@@ -2255,6 +2258,20 @@ function buildCategoryCounts(counts) {
2255
2258
 
2256
2259
  // ../api-routes/src/intelligence.ts
2257
2260
  import { eq as eq11, desc as desc4, and as and2 } from "drizzle-orm";
2261
+ function emptyHealthSnapshot(projectId) {
2262
+ return {
2263
+ id: `no-data:${projectId}`,
2264
+ projectId,
2265
+ runId: null,
2266
+ overallCitedRate: 0,
2267
+ totalPairs: 0,
2268
+ citedPairs: 0,
2269
+ providerBreakdown: {},
2270
+ createdAt: "",
2271
+ status: "no-data",
2272
+ reason: "no-runs-yet"
2273
+ };
2274
+ }
2258
2275
  function mapInsightRow(r) {
2259
2276
  return {
2260
2277
  id: r.id,
@@ -2280,7 +2297,8 @@ function mapHealthRow(r) {
2280
2297
  totalPairs: r.totalPairs,
2281
2298
  citedPairs: r.citedPairs,
2282
2299
  providerBreakdown: parseJsonColumn(r.providerBreakdown, {}),
2283
- createdAt: r.createdAt
2300
+ createdAt: r.createdAt,
2301
+ status: "ready"
2284
2302
  };
2285
2303
  }
2286
2304
  async function intelligenceRoutes(app) {
@@ -2316,7 +2334,7 @@ async function intelligenceRoutes(app) {
2316
2334
  const project = resolveProject(app.db, request.params.name);
2317
2335
  const row = app.db.select().from(healthSnapshots).where(eq11(healthSnapshots.projectId, project.id)).orderBy(desc4(healthSnapshots.createdAt)).limit(1).get();
2318
2336
  if (!row) {
2319
- throw notFound("Health data for project", request.params.name);
2337
+ return reply.send(emptyHealthSnapshot(project.id));
2320
2338
  }
2321
2339
  return reply.send(mapHealthRow(row));
2322
2340
  });
@@ -4533,10 +4551,11 @@ var routeCatalog = [
4533
4551
  method: "get",
4534
4552
  path: "/api/v1/projects/{name}/health/latest",
4535
4553
  summary: "Get latest health snapshot",
4554
+ description: 'Returns the latest health snapshot. Always 200 once the project exists: when no snapshot exists yet (newly-created project, or only failed runs), the response carries `status: "no-data"` with `reason: "no-runs-yet"` and zeroed metrics. Real snapshots carry `status: "ready"`.',
4536
4555
  tags: ["intelligence"],
4537
4556
  parameters: [nameParameter],
4538
4557
  responses: {
4539
- 200: { description: "Health snapshot returned." },
4558
+ 200: { description: "Health snapshot or no-data sentinel returned." },
4540
4559
  404: { description: "Project not found." }
4541
4560
  }
4542
4561
  },
@@ -10279,7 +10298,7 @@ function mapRunRow(row) {
10279
10298
  location: row.location ?? null,
10280
10299
  startedAt: row.startedAt ?? null,
10281
10300
  finishedAt: row.finishedAt ?? null,
10282
- error: row.error ?? null,
10301
+ error: parseRunError(row.error),
10283
10302
  createdAt: row.createdAt
10284
10303
  };
10285
10304
  }
@@ -13489,10 +13508,10 @@ var JobRunner = class {
13489
13508
  const allFailed = totalSnapshotsInserted === 0 && providerErrors.size > 0;
13490
13509
  const someFailed = providerErrors.size > 0;
13491
13510
  if (allFailed) {
13492
- const errorDetail = JSON.stringify(Object.fromEntries(providerErrors));
13511
+ const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
13493
13512
  this.db.update(runs).set({ status: "failed", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq19(runs.id, runId)).run();
13494
13513
  } else if (someFailed) {
13495
- const errorDetail = JSON.stringify(Object.fromEntries(providerErrors));
13514
+ const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
13496
13515
  this.db.update(runs).set({ status: "partial", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq19(runs.id, runId)).run();
13497
13516
  } else {
13498
13517
  this.db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq19(runs.id, runId)).run();
@@ -15295,7 +15314,7 @@ function buildGetHealthTool(ctx) {
15295
15314
  return {
15296
15315
  name: "get_health",
15297
15316
  label: "Get health",
15298
- description: "Latest visibility health snapshot including overall cited rate, pair counts, and per-provider breakdown.",
15317
+ description: 'Latest visibility health snapshot including overall cited rate, pair counts, and per-provider breakdown. Returns `status: "no-data"` with `reason: "no-runs-yet"` and zeroed metrics for projects with no successful runs yet.',
15299
15318
  parameters: HealthSchema,
15300
15319
  execute: async () => {
15301
15320
  const health = await ctx.client.getHealth(ctx.projectName);
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  setGoogleAuthConfig,
18
18
  showFirstRunNotice,
19
19
  trackEvent
20
- } from "./chunk-MGBXRWLX.js";
20
+ } from "./chunk-SZSWQG3J.js";
21
21
  import {
22
22
  CcReleaseSyncStatuses,
23
23
  CliError,
@@ -29,6 +29,7 @@ import {
29
29
  createApiClient,
30
30
  determineAnswerMentioned,
31
31
  effectiveDomains,
32
+ formatRunErrorOneLine,
32
33
  getConfigDir,
33
34
  getConfigPath,
34
35
  isEndpointMissing,
@@ -40,7 +41,7 @@ import {
40
41
  saveConfig,
41
42
  saveConfigPatch,
42
43
  usageError
43
- } from "./chunk-FPZUQADO.js";
44
+ } from "./chunk-KWQCQMPY.js";
44
45
  import {
45
46
  apiKeys,
46
47
  competitors,
@@ -677,7 +678,7 @@ async function backlinksExtract(opts) {
677
678
  return;
678
679
  }
679
680
  if (opts.wait) process.stderr.write("\n");
680
- console.log(`Run ${final.id} (${final.status})${final.error ? " \u2014 " + final.error : ""}`);
681
+ console.log(`Run ${final.id} (${final.status})${final.error ? " \u2014 " + formatRunErrorOneLine(final.error) : ""}`);
681
682
  }
682
683
  async function backlinksCachePrune(opts) {
683
684
  if (!opts.release) {
@@ -4923,7 +4924,14 @@ function printRunDetail(run) {
4923
4924
  if (run.startedAt) console.log(` Started: ${run.startedAt}`);
4924
4925
  if (run.finishedAt) console.log(` Finished: ${run.finishedAt}`);
4925
4926
  if (run.createdAt) console.log(` Created: ${run.createdAt}`);
4926
- if (run.error) console.log(` Error: ${run.error}`);
4927
+ if (run.error) {
4928
+ if (run.error.message) console.log(` Error: ${run.error.message}`);
4929
+ if (run.error.providers) {
4930
+ for (const [provider, detail] of Object.entries(run.error.providers)) {
4931
+ console.log(` Error (${provider}): ${detail.message}`);
4932
+ }
4933
+ }
4934
+ }
4927
4935
  if (run.snapshots && run.snapshots.length > 0) {
4928
4936
  console.log(`
4929
4937
  Snapshots: ${run.snapshots.length}`);
@@ -6002,6 +6010,10 @@ async function showHealth(project, opts) {
6002
6010
  console.log(JSON.stringify(health, null, 2));
6003
6011
  return;
6004
6012
  }
6013
+ if (health.status === "no-data") {
6014
+ console.log("No health data yet \u2014 run a sweep first (canonry run <project>).");
6015
+ return;
6016
+ }
6005
6017
  const rate = (health.overallCitedRate * 100).toFixed(1);
6006
6018
  console.log(`Health: ${rate}% cited (${health.citedPairs}/${health.totalPairs} pairs)`);
6007
6019
  console.log("");
@@ -6689,9 +6701,14 @@ Config saved to ${getConfigPath()}`);
6689
6701
  }
6690
6702
 
6691
6703
  // src/commands/serve.ts
6704
+ function resolveServePort(envPort, configPort) {
6705
+ const trimmed = envPort?.trim();
6706
+ if (trimmed) return parseInt(trimmed, 10);
6707
+ return configPort ?? 4100;
6708
+ }
6692
6709
  async function serveCommand(format = "text") {
6693
6710
  const config = loadConfig();
6694
- const port = parseInt(process.env.CANONRY_PORT ?? "4100", 10);
6711
+ const port = resolveServePort(process.env.CANONRY_PORT, config.port);
6695
6712
  const host = process.env.CANONRY_HOST ?? "127.0.0.1";
6696
6713
  config.port = port;
6697
6714
  const db = createClient(config.database);
@@ -6881,11 +6898,9 @@ function applyServerEnv(values) {
6881
6898
  const port = typeof values.port === "string" ? values.port : void 0;
6882
6899
  const host = typeof values.host === "string" ? values.host : void 0;
6883
6900
  const basePath = typeof values["base-path"] === "string" ? values["base-path"] : void 0;
6884
- process.env.CANONRY_PORT = port ?? "4100";
6901
+ if (port) process.env.CANONRY_PORT = port;
6885
6902
  if (host) process.env.CANONRY_HOST = host;
6886
- else delete process.env.CANONRY_HOST;
6887
6903
  if (basePath) process.env.CANONRY_BASE_PATH = basePath;
6888
- else delete process.env.CANONRY_BASE_PATH;
6889
6904
  }
6890
6905
  var SYSTEM_CLI_COMMANDS = [
6891
6906
  {
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-MGBXRWLX.js";
3
+ } from "./chunk-SZSWQG3J.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-FPZUQADO.js";
6
+ } from "./chunk-KWQCQMPY.js";
7
7
  import "./chunk-PYHANJ3B.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
  export {